kweaver-dolphin 0.1.0__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 (199) hide show
  1. DolphinLanguageSDK/__init__.py +58 -0
  2. dolphin/__init__.py +62 -0
  3. dolphin/cli/__init__.py +20 -0
  4. dolphin/cli/args/__init__.py +9 -0
  5. dolphin/cli/args/parser.py +567 -0
  6. dolphin/cli/builtin_agents/__init__.py +22 -0
  7. dolphin/cli/commands/__init__.py +4 -0
  8. dolphin/cli/interrupt/__init__.py +8 -0
  9. dolphin/cli/interrupt/handler.py +205 -0
  10. dolphin/cli/interrupt/keyboard.py +82 -0
  11. dolphin/cli/main.py +49 -0
  12. dolphin/cli/multimodal/__init__.py +34 -0
  13. dolphin/cli/multimodal/clipboard.py +327 -0
  14. dolphin/cli/multimodal/handler.py +249 -0
  15. dolphin/cli/multimodal/image_processor.py +214 -0
  16. dolphin/cli/multimodal/input_parser.py +149 -0
  17. dolphin/cli/runner/__init__.py +8 -0
  18. dolphin/cli/runner/runner.py +989 -0
  19. dolphin/cli/ui/__init__.py +10 -0
  20. dolphin/cli/ui/console.py +2795 -0
  21. dolphin/cli/ui/input.py +340 -0
  22. dolphin/cli/ui/layout.py +425 -0
  23. dolphin/cli/ui/stream_renderer.py +302 -0
  24. dolphin/cli/utils/__init__.py +8 -0
  25. dolphin/cli/utils/helpers.py +135 -0
  26. dolphin/cli/utils/version.py +49 -0
  27. dolphin/core/__init__.py +107 -0
  28. dolphin/core/agent/__init__.py +10 -0
  29. dolphin/core/agent/agent_state.py +69 -0
  30. dolphin/core/agent/base_agent.py +970 -0
  31. dolphin/core/code_block/__init__.py +0 -0
  32. dolphin/core/code_block/agent_init_block.py +0 -0
  33. dolphin/core/code_block/assign_block.py +98 -0
  34. dolphin/core/code_block/basic_code_block.py +1865 -0
  35. dolphin/core/code_block/explore_block.py +1327 -0
  36. dolphin/core/code_block/explore_block_v2.py +712 -0
  37. dolphin/core/code_block/explore_strategy.py +672 -0
  38. dolphin/core/code_block/judge_block.py +220 -0
  39. dolphin/core/code_block/prompt_block.py +32 -0
  40. dolphin/core/code_block/skill_call_deduplicator.py +291 -0
  41. dolphin/core/code_block/tool_block.py +129 -0
  42. dolphin/core/common/__init__.py +17 -0
  43. dolphin/core/common/constants.py +176 -0
  44. dolphin/core/common/enums.py +1173 -0
  45. dolphin/core/common/exceptions.py +133 -0
  46. dolphin/core/common/multimodal.py +539 -0
  47. dolphin/core/common/object_type.py +165 -0
  48. dolphin/core/common/output_format.py +432 -0
  49. dolphin/core/common/types.py +36 -0
  50. dolphin/core/config/__init__.py +16 -0
  51. dolphin/core/config/global_config.py +1289 -0
  52. dolphin/core/config/ontology_config.py +133 -0
  53. dolphin/core/context/__init__.py +12 -0
  54. dolphin/core/context/context.py +1580 -0
  55. dolphin/core/context/context_manager.py +161 -0
  56. dolphin/core/context/var_output.py +82 -0
  57. dolphin/core/context/variable_pool.py +356 -0
  58. dolphin/core/context_engineer/__init__.py +41 -0
  59. dolphin/core/context_engineer/config/__init__.py +5 -0
  60. dolphin/core/context_engineer/config/settings.py +402 -0
  61. dolphin/core/context_engineer/core/__init__.py +7 -0
  62. dolphin/core/context_engineer/core/budget_manager.py +327 -0
  63. dolphin/core/context_engineer/core/context_assembler.py +583 -0
  64. dolphin/core/context_engineer/core/context_manager.py +637 -0
  65. dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
  66. dolphin/core/context_engineer/example/incremental_example.py +267 -0
  67. dolphin/core/context_engineer/example/traditional_example.py +334 -0
  68. dolphin/core/context_engineer/services/__init__.py +5 -0
  69. dolphin/core/context_engineer/services/compressor.py +399 -0
  70. dolphin/core/context_engineer/utils/__init__.py +6 -0
  71. dolphin/core/context_engineer/utils/context_utils.py +441 -0
  72. dolphin/core/context_engineer/utils/message_formatter.py +270 -0
  73. dolphin/core/context_engineer/utils/token_utils.py +139 -0
  74. dolphin/core/coroutine/__init__.py +15 -0
  75. dolphin/core/coroutine/context_snapshot.py +154 -0
  76. dolphin/core/coroutine/context_snapshot_profile.py +922 -0
  77. dolphin/core/coroutine/context_snapshot_store.py +268 -0
  78. dolphin/core/coroutine/execution_frame.py +145 -0
  79. dolphin/core/coroutine/execution_state_registry.py +161 -0
  80. dolphin/core/coroutine/resume_handle.py +101 -0
  81. dolphin/core/coroutine/step_result.py +101 -0
  82. dolphin/core/executor/__init__.py +18 -0
  83. dolphin/core/executor/debug_controller.py +630 -0
  84. dolphin/core/executor/dolphin_executor.py +1063 -0
  85. dolphin/core/executor/executor.py +624 -0
  86. dolphin/core/flags/__init__.py +27 -0
  87. dolphin/core/flags/definitions.py +49 -0
  88. dolphin/core/flags/manager.py +113 -0
  89. dolphin/core/hook/__init__.py +95 -0
  90. dolphin/core/hook/expression_evaluator.py +499 -0
  91. dolphin/core/hook/hook_dispatcher.py +380 -0
  92. dolphin/core/hook/hook_types.py +248 -0
  93. dolphin/core/hook/isolated_variable_pool.py +284 -0
  94. dolphin/core/interfaces.py +53 -0
  95. dolphin/core/llm/__init__.py +0 -0
  96. dolphin/core/llm/llm.py +495 -0
  97. dolphin/core/llm/llm_call.py +100 -0
  98. dolphin/core/llm/llm_client.py +1285 -0
  99. dolphin/core/llm/message_sanitizer.py +120 -0
  100. dolphin/core/logging/__init__.py +20 -0
  101. dolphin/core/logging/logger.py +526 -0
  102. dolphin/core/message/__init__.py +8 -0
  103. dolphin/core/message/compressor.py +749 -0
  104. dolphin/core/parser/__init__.py +8 -0
  105. dolphin/core/parser/parser.py +405 -0
  106. dolphin/core/runtime/__init__.py +10 -0
  107. dolphin/core/runtime/runtime_graph.py +926 -0
  108. dolphin/core/runtime/runtime_instance.py +446 -0
  109. dolphin/core/skill/__init__.py +14 -0
  110. dolphin/core/skill/context_retention.py +157 -0
  111. dolphin/core/skill/skill_function.py +686 -0
  112. dolphin/core/skill/skill_matcher.py +282 -0
  113. dolphin/core/skill/skillkit.py +700 -0
  114. dolphin/core/skill/skillset.py +72 -0
  115. dolphin/core/trajectory/__init__.py +10 -0
  116. dolphin/core/trajectory/recorder.py +189 -0
  117. dolphin/core/trajectory/trajectory.py +522 -0
  118. dolphin/core/utils/__init__.py +9 -0
  119. dolphin/core/utils/cache_kv.py +212 -0
  120. dolphin/core/utils/tools.py +340 -0
  121. dolphin/lib/__init__.py +93 -0
  122. dolphin/lib/debug/__init__.py +8 -0
  123. dolphin/lib/debug/visualizer.py +409 -0
  124. dolphin/lib/memory/__init__.py +28 -0
  125. dolphin/lib/memory/async_processor.py +220 -0
  126. dolphin/lib/memory/llm_calls.py +195 -0
  127. dolphin/lib/memory/manager.py +78 -0
  128. dolphin/lib/memory/sandbox.py +46 -0
  129. dolphin/lib/memory/storage.py +245 -0
  130. dolphin/lib/memory/utils.py +51 -0
  131. dolphin/lib/ontology/__init__.py +12 -0
  132. dolphin/lib/ontology/basic/__init__.py +0 -0
  133. dolphin/lib/ontology/basic/base.py +102 -0
  134. dolphin/lib/ontology/basic/concept.py +130 -0
  135. dolphin/lib/ontology/basic/object.py +11 -0
  136. dolphin/lib/ontology/basic/relation.py +63 -0
  137. dolphin/lib/ontology/datasource/__init__.py +27 -0
  138. dolphin/lib/ontology/datasource/datasource.py +66 -0
  139. dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
  140. dolphin/lib/ontology/datasource/sql.py +845 -0
  141. dolphin/lib/ontology/mapping.py +177 -0
  142. dolphin/lib/ontology/ontology.py +733 -0
  143. dolphin/lib/ontology/ontology_context.py +16 -0
  144. dolphin/lib/ontology/ontology_manager.py +107 -0
  145. dolphin/lib/skill_results/__init__.py +31 -0
  146. dolphin/lib/skill_results/cache_backend.py +559 -0
  147. dolphin/lib/skill_results/result_processor.py +181 -0
  148. dolphin/lib/skill_results/result_reference.py +179 -0
  149. dolphin/lib/skill_results/skillkit_hook.py +324 -0
  150. dolphin/lib/skill_results/strategies.py +328 -0
  151. dolphin/lib/skill_results/strategy_registry.py +150 -0
  152. dolphin/lib/skillkits/__init__.py +44 -0
  153. dolphin/lib/skillkits/agent_skillkit.py +155 -0
  154. dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
  155. dolphin/lib/skillkits/env_skillkit.py +250 -0
  156. dolphin/lib/skillkits/mcp_adapter.py +616 -0
  157. dolphin/lib/skillkits/mcp_skillkit.py +771 -0
  158. dolphin/lib/skillkits/memory_skillkit.py +650 -0
  159. dolphin/lib/skillkits/noop_skillkit.py +31 -0
  160. dolphin/lib/skillkits/ontology_skillkit.py +89 -0
  161. dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
  162. dolphin/lib/skillkits/resource/__init__.py +52 -0
  163. dolphin/lib/skillkits/resource/models/__init__.py +6 -0
  164. dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
  165. dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
  166. dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
  167. dolphin/lib/skillkits/resource/skill_cache.py +215 -0
  168. dolphin/lib/skillkits/resource/skill_loader.py +395 -0
  169. dolphin/lib/skillkits/resource/skill_validator.py +406 -0
  170. dolphin/lib/skillkits/resource_skillkit.py +11 -0
  171. dolphin/lib/skillkits/search_skillkit.py +163 -0
  172. dolphin/lib/skillkits/sql_skillkit.py +274 -0
  173. dolphin/lib/skillkits/system_skillkit.py +509 -0
  174. dolphin/lib/skillkits/vm_skillkit.py +65 -0
  175. dolphin/lib/utils/__init__.py +9 -0
  176. dolphin/lib/utils/data_process.py +207 -0
  177. dolphin/lib/utils/handle_progress.py +178 -0
  178. dolphin/lib/utils/security.py +139 -0
  179. dolphin/lib/utils/text_retrieval.py +462 -0
  180. dolphin/lib/vm/__init__.py +11 -0
  181. dolphin/lib/vm/env_executor.py +895 -0
  182. dolphin/lib/vm/python_session_manager.py +453 -0
  183. dolphin/lib/vm/vm.py +610 -0
  184. dolphin/sdk/__init__.py +60 -0
  185. dolphin/sdk/agent/__init__.py +12 -0
  186. dolphin/sdk/agent/agent_factory.py +236 -0
  187. dolphin/sdk/agent/dolphin_agent.py +1106 -0
  188. dolphin/sdk/api/__init__.py +4 -0
  189. dolphin/sdk/runtime/__init__.py +8 -0
  190. dolphin/sdk/runtime/env.py +363 -0
  191. dolphin/sdk/skill/__init__.py +10 -0
  192. dolphin/sdk/skill/global_skills.py +706 -0
  193. dolphin/sdk/skill/traditional_toolkit.py +260 -0
  194. kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
  195. kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
  196. kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
  197. kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
  198. kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
  199. kweaver_dolphin-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,338 @@
1
+ from typing import Any, Dict, List, Optional
2
+ import re
3
+ import datetime
4
+
5
+ from sqlalchemy import create_engine, inspect
6
+ from sqlalchemy.engine import Engine
7
+
8
+ # Add NullPool import
9
+ from sqlalchemy.pool import NullPool
10
+
11
+ from dolphin.core.common.enums import Messages
12
+ from dolphin.lib.ontology.datasource.datasource import DataSourceType
13
+ from dolphin.lib.ontology.datasource.sql import DataSourceSql
14
+
15
+ from dolphin.core.logging.logger import get_logger
16
+
17
+ logger = get_logger("ontology")
18
+
19
+
20
+ def _camelCase(s: str) -> str:
21
+ """Convert a string separated by underscores or spaces to camel case"""
22
+ s = re.sub(r"[_\-]+", " ", s).title().replace(" ", "")
23
+ return s[0].lower() + s[1:] if s else ""
24
+
25
+
26
+ class DataSourceOracle(DataSourceSql):
27
+ """Oracle data source implementation"""
28
+
29
+ def __init__(self, name: str, config: Dict[str, Any]):
30
+ # Pass the correct type DataSourceType.ORACLE
31
+ super().__init__(name, DataSourceType.ORACLE, config)
32
+ self._type = DataSourceType.ORACLE # Store specific types
33
+ # The default Oracle port is 1521
34
+ self.port = config.get("port", 1521)
35
+
36
+ def connect(self) -> Engine:
37
+ """Connect to Oracle Database"""
38
+ if self._engine:
39
+ logger.debug(f"Already connected to {self.name} , reconnecting")
40
+ self.close()
41
+
42
+ try:
43
+ # Using python-oracledb's thin mode, no local Oracle Client required
44
+ import oracledb # Import only when needed
45
+
46
+ try:
47
+ # Explicitly disable thick mode to avoid searching for local Client
48
+ oracledb.defaults.thick_mode = False
49
+ except Exception:
50
+ pass
51
+
52
+ # Build SQLAlchemy connection URL (oracle+oracledb dialect)
53
+ # Prioritize using service_name,then SID;if neither configured, treat database as service_name
54
+ service_name = (
55
+ self.config.get("service_name") or self.config.get("service") or None
56
+ )
57
+ sid = self.config.get("sid")
58
+
59
+ if service_name:
60
+ connection_url = (
61
+ f"oracle+oracledb://{self.username}:{self.password}@{self.host}:{self.port}"
62
+ f"/?service_name={service_name}"
63
+ )
64
+ elif sid:
65
+ connection_url = (
66
+ f"oracle+oracledb://{self.username}:{self.password}@{self.host}:{self.port}"
67
+ f"/?sid={sid}"
68
+ )
69
+ elif self.database:
70
+ connection_url = (
71
+ f"oracle+oracledb://{self.username}:{self.password}@{self.host}:{self.port}"
72
+ f"/?service_name={self.database}"
73
+ )
74
+ else:
75
+ connection_url = f"oracle+oracledb://{self.username}:{self.password}@{self.host}:{self.port}"
76
+
77
+ # Disable connection pooling using NullPool
78
+ self._engine = create_engine(
79
+ connection_url,
80
+ poolclass=NullPool, # Disable connection pooling
81
+ )
82
+
83
+ self._inspector = inspect(self._engine)
84
+ logger.info(f"Successfully connected to Oracle database: {self.name}")
85
+ return self._engine
86
+ except ImportError:
87
+ logger.error(
88
+ f"connect to Oracle database {self.name} failed: missing 'oracledb' library. Please run 'pip install oracledb'"
89
+ )
90
+ self._engine = None
91
+ raise ConnectionError(f"Missing Oracle driver: {self.name}")
92
+ except Exception as e: # Other unexpected errors
93
+ # If available, try to identify the database error types of oracledb
94
+ try:
95
+ import oracledb as _odb
96
+
97
+ if isinstance(e, _odb.Error):
98
+ logger.error(f"connect to Oracle databasefailed {self.name}: {e}")
99
+ self._engine = None
100
+ raise ConnectionError(f"无法connect to到 Oracle database: {self.name}, {e}")
101
+ except Exception:
102
+ pass
103
+
104
+ logger.error(f"connect to Oracle unknown error occurred {self.name}: {e}")
105
+ self._engine = None
106
+ raise ConnectionError(f"connect to Oracle unknown error occurred: {self.name}, {e}")
107
+
108
+ def test_connection(self) -> bool:
109
+ """Test Oracle connection, using DUAL for Oracle compatibility.
110
+
111
+ Returns:
112
+ bool: True if the connection is successful, False otherwise.
113
+ """
114
+ originalConnectionState = self._engine
115
+ connToClose = None
116
+ try:
117
+ if not self._engine:
118
+ connToClose = self.connect()
119
+ if not self._engine:
120
+ logger.warning(f"测试connect to {self.name} failed:无法建立connect to")
121
+ return False
122
+ # Oracle requires FROM DUAL
123
+ self.executeQuery("SELECT 1 FROM DUAL", fetchColumns=False)
124
+ logger.info(f"测试connect to {self.name} successful")
125
+ return True
126
+ except Exception as e:
127
+ logger.error(f"测试connect tofailed {self.name}: {e}")
128
+ return False
129
+ finally:
130
+ # If the connection is temporarily established for testing, close it.
131
+ if connToClose and connToClose == self._engine:
132
+ self.close()
133
+ # Restore the original connection status (if there was already a connection before the test)
134
+ elif originalConnectionState and not self._engine:
135
+ self._engine = originalConnectionState
136
+
137
+ def get_schema(self) -> Dict[str, List[Dict[str, str]]]:
138
+ """Get Oracle database schema information (table name -> list of column information).
139
+
140
+ Returns:
141
+ dict: A mapping of table name to column info list.
142
+ """
143
+ if self.schema:
144
+ return self.schema
145
+
146
+ conn = self._engine
147
+ should_close_conn = False
148
+ if not conn:
149
+ conn = self.connect() # Try to connect
150
+ if not conn:
151
+ raise ConnectionError(f"无法Get模式,database未connect to: {self.name}")
152
+ should_close_conn = True
153
+
154
+ schema: Dict[str, List[Dict[str, str]]] = {}
155
+ try:
156
+ # Use self._inspector to get the table name, which is more in line with SQLAlchemy's approach
157
+ if not self._inspector: # Ensure inspector exists
158
+ if not conn: # If conn was not successfully established previously
159
+ conn = self.connect()
160
+ if not conn:
161
+ raise ConnectionError(
162
+ f"无法Get模式,database未connect to: {self.name}"
163
+ )
164
+ self._inspector = inspect(conn)
165
+
166
+ tables = self._inspector.get_table_names()
167
+
168
+ # Get column names and types for each table
169
+ for table_name in tables:
170
+ # Use self._inspector to get column information
171
+ columns_info = self._inspector.get_columns(table_name)
172
+
173
+ current_table_cols = []
174
+ for column_data in columns_info:
175
+ # column_data is a dictionary containing keys such as 'name', 'type', 'nullable', 'default', etc.
176
+ # 'type' is usually a SQLAlchemy type object, which needs to be converted to a string.
177
+ col_name = column_data.get("name")
178
+ col_type_obj = column_data.get("type")
179
+
180
+ if col_name and col_type_obj is not None: # Ensure column names and types exist
181
+ # Convert SQLAlchemy type objects to their string representations
182
+ # For example: VARCHAR2(length=50), NUMBER(precision=10, scale=2), DATE
183
+ col_type_str = str(col_type_obj)
184
+ current_table_cols.append(
185
+ {"name": col_name, "type": col_type_str}
186
+ )
187
+ elif col_name: # Unknown type, but column name exists
188
+ current_table_cols.append(
189
+ {"name": col_name, "type": "UNKNOWN"}
190
+ ) # Or record a default value
191
+ logger.warning(
192
+ f"Column '{col_name}' in table '{table_name}' has an unknown type."
193
+ )
194
+
195
+ if current_table_cols: # Only add to schema when the table has columns
196
+ schema[table_name] = current_table_cols
197
+ else:
198
+ logger.info(
199
+ f"Table '{table_name}' has no columns or columns could not be retrieved."
200
+ )
201
+
202
+ logger.debug(f"Get到 {self.name} schema: {len(schema)} tables")
203
+ self.schema = schema
204
+ return schema
205
+ except Exception as err: # Catch all database-related errors
206
+ logger.error(f"Get Oracle 模式failed {self.name}: {err}")
207
+ # Ensure that if the connection is temporarily open, it will be closed when an error occurs
208
+ if (
209
+ should_close_conn and conn and conn == self._engine
210
+ ): # If this connection was specifically opened for this method
211
+ conn.dispose()
212
+ self._engine = None # Reset engine state
213
+ self._inspector = None # Reset inspector
214
+ raise RuntimeError(f"Get Oracle 模式failed: {err}") from err
215
+ finally:
216
+ if should_close_conn and conn:
217
+ if conn == self._engine: # Only close when this method creates self._engine
218
+ conn.dispose()
219
+ self._engine = None
220
+ self._inspector = None # Also clear the inspector
221
+ elif conn != self._engine: # If it's a temporarily created conn and not self._engine
222
+ conn.dispose()
223
+
224
+ def sampleData(self, conceptName: str, count: int = 10) -> Messages:
225
+ """Retrieve sample data from an Oracle data source for a specified Concept name.
226
+
227
+ It converts the Concept name back to a possible table name (assuming the naming convention used in the scan method),
228
+ then queries that table to retrieve the specified number of sample rows.
229
+
230
+ Args:
231
+ conceptName (str): The name of the Concept for which to retrieve sample data.
232
+ count (int): The number of sample rows to retrieve, defaults to 10.
233
+
234
+ Returns:
235
+ Messages: A list of dictionaries, each representing a row of data,
236
+ where keys are column names and values are corresponding data.
237
+ Returns an empty list if the Concept is not found or an error occurs.
238
+ """
239
+ if count <= 0:
240
+ logger.info(
241
+ f"Sample count is {count}, returning empty list for concept '{conceptName}' in {self.name}."
242
+ )
243
+ return []
244
+
245
+ target_table_name: Optional[str] = None
246
+ actual_column_names: List[str] = []
247
+
248
+ try:
249
+ db_schema = self.get_schema() # This might connect if not connected.
250
+ if not db_schema:
251
+ logger.warning(
252
+ f"Could not retrieve schema for {self.name} to find concept '{conceptName}'."
253
+ )
254
+ return []
255
+
256
+ for table_name_from_schema, columns_details in db_schema.items():
257
+ # Ensure table_name_from_schema is a string for _camelCase
258
+ if not isinstance(table_name_from_schema, str):
259
+ logger.warning(
260
+ f"Skipping non-string table name in schema: {table_name_from_schema}"
261
+ )
262
+ continue
263
+
264
+ generated_concept_name = _camelCase(table_name_from_schema).capitalize()
265
+ if generated_concept_name == conceptName:
266
+ target_table_name = table_name_from_schema
267
+ actual_column_names = [
268
+ col_info["name"]
269
+ for col_info in columns_details
270
+ if col_info.get("name")
271
+ ]
272
+ if not actual_column_names:
273
+ logger.warning(
274
+ f"Concept '{conceptName}' (Table '{target_table_name}') found in {self.name} but has no columns. Cannot sample data."
275
+ )
276
+ return [] # Cannot select data if no columns
277
+ break # Found the table
278
+
279
+ if not target_table_name:
280
+ logger.warning(
281
+ f"Concept '{conceptName}' not found as a discoverable table in datasource '{self.name}'."
282
+ )
283
+ return []
284
+
285
+ # Oracle uses double quotes to quote identifiers, supporting case sensitivity
286
+ quoted_column_names_str = ", ".join(
287
+ [f'"{col}"' for col in actual_column_names]
288
+ )
289
+ # Oracle limits rows using ROWNUM or FETCH FIRST (Oracle 12c+)
290
+ # ROWNUM method with better compatibility is used here
291
+ sql_query = f'SELECT {quoted_column_names_str} FROM "{target_table_name}" WHERE ROWNUM <= {count}'
292
+
293
+ logger.debug(
294
+ f"Executing sample data query for concept '{conceptName}' on {self.name}: {sql_query}"
295
+ )
296
+ query_result = self.executeQuery(
297
+ sql_query
298
+ ) # fetchColumns is True by default
299
+
300
+ result_columns = query_result.get("columns", [])
301
+ result_data_rows = query_result.get("data", [])
302
+
303
+ if not result_columns and result_data_rows:
304
+ logger.warning(
305
+ f"Query for concept '{conceptName}' in {self.name} returned data but no column names. This might indicate an issue with executeQuery or the underlying table structure."
306
+ )
307
+
308
+ formatted_samples: Messages = []
309
+ for row_tuple in result_data_rows:
310
+ processed_row = []
311
+ for item in row_tuple:
312
+ if isinstance(
313
+ item, (datetime.datetime, datetime.date, datetime.time)
314
+ ):
315
+ processed_row.append(item.isoformat())
316
+ else:
317
+ processed_row.append(item)
318
+
319
+ if len(processed_row) == len(result_columns):
320
+ formatted_samples.append(dict(zip(result_columns, processed_row)))
321
+ else:
322
+ logger.warning(
323
+ f"Row data length mismatch for concept '{conceptName}' in {self.name}. "
324
+ f"Expected {len(result_columns)} columns based on query result, got {len(processed_row)}. Row: {processed_row}"
325
+ )
326
+ return formatted_samples
327
+
328
+ except ConnectionError as ce:
329
+ logger.error(
330
+ f"Connection error while fetching sample data for concept '{conceptName}' from {self.name}: {ce}"
331
+ )
332
+ return []
333
+ except Exception as e:
334
+ logger.error(
335
+ f"Error fetching sample data for concept '{conceptName}' (table: {target_table_name or 'unknown'}) from {self.name}: {e}",
336
+ exc_info=True,
337
+ )
338
+ return []