iris-vector-rag 0.2.1__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.
- adapters/rag_templates_bridge.py +578 -0
- common/__init__.py +1 -0
- common/batch_utils.py +294 -0
- common/config.py +58 -0
- common/connection_manager.py +242 -0
- common/connection_pool.py +445 -0
- common/database.py +141 -0
- common/database_schema_manager.py +289 -0
- common/db_init_with_indexes.py +181 -0
- common/db_vector_utils.py +181 -0
- common/environment_manager.py +220 -0
- common/huggingface_utils.py +206 -0
- common/iris_client.py +92 -0
- common/iris_connection_manager.py +411 -0
- common/iris_connection_pool.py +360 -0
- common/iris_connector.py +71 -0
- common/iris_dbapi_connector.py +323 -0
- common/iris_index_utils.py +195 -0
- common/iris_sql_utils.py +704 -0
- common/iris_stream_reader.py +166 -0
- common/llm_cache_config.py +308 -0
- common/llm_cache_iris.py +671 -0
- common/llm_cache_manager.py +911 -0
- common/utils.py +559 -0
- common/vector_sql_utils.py +584 -0
- evaluation_framework/__init__.py +66 -0
- evaluation_framework/biomedical_question_generator.py +826 -0
- evaluation_framework/comparative_analysis_system.py +1173 -0
- evaluation_framework/empirical_reporting.py +1298 -0
- evaluation_framework/evaluation_orchestrator.py +1123 -0
- evaluation_framework/execute_production_evaluation.py +382 -0
- evaluation_framework/pmc_data_pipeline.py +1332 -0
- evaluation_framework/production_evaluation_config.py +353 -0
- evaluation_framework/ragas_metrics_framework.py +1013 -0
- evaluation_framework/real_production_evaluation.py +544 -0
- evaluation_framework/run_10k_evaluation.py +175 -0
- evaluation_framework/simple_test.py +364 -0
- evaluation_framework/statistical_evaluation_methodology.py +1016 -0
- evaluation_framework/test_framework.py +281 -0
- evaluation_framework/test_iris_connectivity.py +244 -0
- evaluation_framework/test_small_scale_evaluation.py +432 -0
- evaluation_framework/true_e2e_evaluation.py +639 -0
- evaluation_framework/visualization_engine.py +585 -0
- iris_rag/__init__.py +284 -0
- iris_rag/config/default_config.yaml +295 -0
- iris_vector_rag-0.2.1.dist-info/METADATA +499 -0
- iris_vector_rag-0.2.1.dist-info/RECORD +50 -0
- iris_vector_rag-0.2.1.dist-info/WHEEL +5 -0
- iris_vector_rag-0.2.1.dist-info/entry_points.txt +2 -0
- iris_vector_rag-0.2.1.dist-info/top_level.txt +4 -0
common/iris_sql_utils.py
ADDED
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IRIS SQL Utilities - Proper IRIS SQL Syntax Support
|
|
3
|
+
|
|
4
|
+
This module provides utilities for working with IRIS-specific SQL syntax,
|
|
5
|
+
particularly for vector operations and index creation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
# Import the DBAPI connector for self-managed connections
|
|
14
|
+
try:
|
|
15
|
+
from common.iris_dbapi_connector import get_iris_dbapi_connection
|
|
16
|
+
except ImportError:
|
|
17
|
+
logger.warning(
|
|
18
|
+
"Could not import get_iris_dbapi_connection. Self-managed connections will not be available."
|
|
19
|
+
)
|
|
20
|
+
get_iris_dbapi_connection = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class IRISSQLUtils:
|
|
24
|
+
"""Utilities for IRIS-specific SQL operations."""
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def create_vector_index(
|
|
28
|
+
connection: Any,
|
|
29
|
+
table_name: str,
|
|
30
|
+
column_name: str,
|
|
31
|
+
index_name: Optional[str] = None,
|
|
32
|
+
metric: str = "COSINE",
|
|
33
|
+
index_type: str = "HNSW",
|
|
34
|
+
) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Create a vector index using proper IRIS syntax.
|
|
37
|
+
|
|
38
|
+
Uses the correct IRIS HNSW index syntax from db_init_complete.sql:
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_hnsw_source_embedding
|
|
40
|
+
ON RAG.SourceDocuments (embedding)
|
|
41
|
+
AS HNSW(M=16, efConstruction=200, Distance='COSINE');
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
connection: Database connection
|
|
45
|
+
table_name: Name of the table
|
|
46
|
+
column_name: Name of the vector column
|
|
47
|
+
index_name: Optional custom index name
|
|
48
|
+
metric: Vector similarity metric (COSINE, DOT, EUCLIDEAN)
|
|
49
|
+
index_type: Index type (HNSW, FLAT) - currently only HNSW supported
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
True if successful, False otherwise
|
|
53
|
+
"""
|
|
54
|
+
if not index_name:
|
|
55
|
+
# Clean table name for index naming
|
|
56
|
+
clean_table = table_name.replace(".", "_")
|
|
57
|
+
index_name = f"{clean_table}_{column_name}_vector_idx"
|
|
58
|
+
|
|
59
|
+
# IRIS vector index syntax using AS HNSW (from db_init_complete.sql)
|
|
60
|
+
# Note: IRIS doesn't support "IF NOT EXISTS" in CREATE INDEX statements
|
|
61
|
+
sql = f"""
|
|
62
|
+
CREATE INDEX {index_name}
|
|
63
|
+
ON {table_name} ({column_name})
|
|
64
|
+
AS HNSW(M=16, efConstruction=200, Distance='{metric}')
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
# Check if index already exists first
|
|
68
|
+
if IRISSQLUtils.check_index_exists(connection, index_name, table_name):
|
|
69
|
+
logger.debug(f"Vector index {index_name} already exists")
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
cursor = connection.cursor()
|
|
74
|
+
cursor.execute(sql)
|
|
75
|
+
connection.commit()
|
|
76
|
+
cursor.close()
|
|
77
|
+
logger.info(
|
|
78
|
+
f"✓ Created vector index {index_name} on {table_name}.{column_name}"
|
|
79
|
+
)
|
|
80
|
+
return True
|
|
81
|
+
except Exception as e:
|
|
82
|
+
error_msg = str(e).lower()
|
|
83
|
+
# Handle IRIS-specific error codes and messages
|
|
84
|
+
if (
|
|
85
|
+
"already exists" in error_msg
|
|
86
|
+
or "duplicate" in error_msg
|
|
87
|
+
or "sqlcode: <-324>" in error_msg
|
|
88
|
+
or "index with this name already defined" in error_msg
|
|
89
|
+
):
|
|
90
|
+
logger.debug(f"Vector index {index_name} already exists")
|
|
91
|
+
return True
|
|
92
|
+
else:
|
|
93
|
+
logger.error(f"Failed to create vector index {index_name}: {e}")
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def create_simple_index(
|
|
98
|
+
connection: Any,
|
|
99
|
+
table_name: str,
|
|
100
|
+
column_name: str,
|
|
101
|
+
index_name: Optional[str] = None,
|
|
102
|
+
) -> bool:
|
|
103
|
+
"""
|
|
104
|
+
Create a simple (non-vector) index using IRIS syntax.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
connection: Database connection
|
|
108
|
+
table_name: Name of the table
|
|
109
|
+
column_name: Name of the column
|
|
110
|
+
index_name: Optional custom index name
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
True if successful, False otherwise
|
|
114
|
+
"""
|
|
115
|
+
if not index_name:
|
|
116
|
+
clean_table = table_name.replace(".", "_")
|
|
117
|
+
index_name = f"{clean_table}_{column_name}_idx"
|
|
118
|
+
|
|
119
|
+
# Simple index syntax for IRIS
|
|
120
|
+
sql = f"CREATE INDEX {index_name} ON {table_name} ({column_name})"
|
|
121
|
+
|
|
122
|
+
# Check if index already exists first
|
|
123
|
+
if IRISSQLUtils.check_index_exists(connection, index_name, table_name):
|
|
124
|
+
logger.debug(f"Index {index_name} already exists")
|
|
125
|
+
return True
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
cursor = connection.cursor()
|
|
129
|
+
cursor.execute(sql)
|
|
130
|
+
connection.commit()
|
|
131
|
+
cursor.close()
|
|
132
|
+
logger.info(f"✓ Created index {index_name} on {table_name}.{column_name}")
|
|
133
|
+
return True
|
|
134
|
+
except Exception as e:
|
|
135
|
+
error_msg = str(e).lower()
|
|
136
|
+
# Handle IRIS-specific error codes and messages
|
|
137
|
+
if (
|
|
138
|
+
"already exists" in error_msg
|
|
139
|
+
or "duplicate" in error_msg
|
|
140
|
+
or "sqlcode: <-324>" in error_msg
|
|
141
|
+
or "index with this name already defined" in error_msg
|
|
142
|
+
):
|
|
143
|
+
logger.debug(f"Index {index_name} already exists")
|
|
144
|
+
return True
|
|
145
|
+
else:
|
|
146
|
+
logger.error(f"Failed to create index {index_name}: {e}")
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def check_table_exists(connection: Any, table_name: str) -> bool:
|
|
151
|
+
"""
|
|
152
|
+
Check if a table exists using IRIS-compatible syntax.
|
|
153
|
+
Uses a self-managed database connection to avoid "DataRow is inaccessible" errors.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
connection: Database connection (kept for compatibility, but not used)
|
|
157
|
+
table_name: Name of the table (schema.table format)
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
True if table exists, False otherwise
|
|
161
|
+
"""
|
|
162
|
+
# Use self-managed connection to avoid interference with other operations
|
|
163
|
+
if get_iris_dbapi_connection is None:
|
|
164
|
+
logger.warning(
|
|
165
|
+
"Self-managed connections not available, falling back to provided connection"
|
|
166
|
+
)
|
|
167
|
+
return IRISSQLUtils._check_table_exists_with_connection(
|
|
168
|
+
connection, table_name
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
temp_connection = None
|
|
172
|
+
cursor = None
|
|
173
|
+
try:
|
|
174
|
+
temp_connection = get_iris_dbapi_connection()
|
|
175
|
+
if not temp_connection:
|
|
176
|
+
logger.error(
|
|
177
|
+
"Failed to obtain temporary connection for table existence check"
|
|
178
|
+
)
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
# Split schema and table name
|
|
182
|
+
if "." in table_name:
|
|
183
|
+
schema, table = table_name.split(".", 1)
|
|
184
|
+
else:
|
|
185
|
+
schema = "USER" # Default schema
|
|
186
|
+
table = table_name
|
|
187
|
+
|
|
188
|
+
cursor = temp_connection.cursor()
|
|
189
|
+
# Use IRIS system tables to check existence
|
|
190
|
+
cursor.execute(
|
|
191
|
+
"""
|
|
192
|
+
SELECT COUNT(*)
|
|
193
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
194
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
195
|
+
""",
|
|
196
|
+
[schema, table],
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
result = cursor.fetchone()
|
|
200
|
+
return result[0] > 0
|
|
201
|
+
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(f"Failed to check table existence for {table_name}: {e}")
|
|
204
|
+
return False
|
|
205
|
+
finally:
|
|
206
|
+
# Ensure cleanup of temporary connection and cursor
|
|
207
|
+
if cursor:
|
|
208
|
+
try:
|
|
209
|
+
cursor.close()
|
|
210
|
+
except:
|
|
211
|
+
pass
|
|
212
|
+
if temp_connection:
|
|
213
|
+
try:
|
|
214
|
+
temp_connection.close()
|
|
215
|
+
except:
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def _check_table_exists_with_connection(connection: Any, table_name: str) -> bool:
|
|
220
|
+
"""
|
|
221
|
+
Fallback method using provided connection for table existence check.
|
|
222
|
+
"""
|
|
223
|
+
try:
|
|
224
|
+
# Split schema and table name
|
|
225
|
+
if "." in table_name:
|
|
226
|
+
schema, table = table_name.split(".", 1)
|
|
227
|
+
else:
|
|
228
|
+
schema = "USER" # Default schema
|
|
229
|
+
table = table_name
|
|
230
|
+
|
|
231
|
+
cursor = connection.cursor()
|
|
232
|
+
# Use IRIS system tables to check existence
|
|
233
|
+
cursor.execute(
|
|
234
|
+
"""
|
|
235
|
+
SELECT COUNT(*)
|
|
236
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
237
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
238
|
+
""",
|
|
239
|
+
[schema, table],
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
result = cursor.fetchone()
|
|
243
|
+
cursor.close()
|
|
244
|
+
return result[0] > 0
|
|
245
|
+
|
|
246
|
+
except Exception as e:
|
|
247
|
+
logger.error(f"Failed to check table existence for {table_name}: {e}")
|
|
248
|
+
return False
|
|
249
|
+
|
|
250
|
+
@staticmethod
|
|
251
|
+
def check_index_exists(
|
|
252
|
+
connection: Any, index_name: str, table_name: Optional[str] = None
|
|
253
|
+
) -> bool:
|
|
254
|
+
"""
|
|
255
|
+
Check if an index exists using IRIS-compatible syntax.
|
|
256
|
+
Uses a self-managed database connection to avoid "DataRow is inaccessible" errors.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
connection: Database connection (kept for compatibility, but not used)
|
|
260
|
+
index_name: Name of the index
|
|
261
|
+
table_name: Optional table name to filter by
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
True if index exists, False otherwise
|
|
265
|
+
"""
|
|
266
|
+
# Use self-managed connection to avoid interference with other operations
|
|
267
|
+
if get_iris_dbapi_connection is None:
|
|
268
|
+
logger.warning(
|
|
269
|
+
"Self-managed connections not available, falling back to provided connection"
|
|
270
|
+
)
|
|
271
|
+
return IRISSQLUtils._check_index_exists_with_connection(
|
|
272
|
+
connection, index_name, table_name
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
temp_connection = None
|
|
276
|
+
cursor = None
|
|
277
|
+
try:
|
|
278
|
+
temp_connection = get_iris_dbapi_connection()
|
|
279
|
+
if not temp_connection:
|
|
280
|
+
logger.error(
|
|
281
|
+
"Failed to obtain temporary connection for index existence check"
|
|
282
|
+
)
|
|
283
|
+
return False
|
|
284
|
+
|
|
285
|
+
cursor = temp_connection.cursor()
|
|
286
|
+
|
|
287
|
+
# IRIS doesn't have INFORMATION_SCHEMA.STATISTICS, use system tables
|
|
288
|
+
if table_name:
|
|
289
|
+
# Split schema and table name
|
|
290
|
+
if "." in table_name:
|
|
291
|
+
schema, table = table_name.split(".", 1)
|
|
292
|
+
else:
|
|
293
|
+
schema = "USER"
|
|
294
|
+
table = table_name
|
|
295
|
+
|
|
296
|
+
# Check for index on specific table
|
|
297
|
+
cursor.execute(
|
|
298
|
+
"""
|
|
299
|
+
SELECT COUNT(*)
|
|
300
|
+
FROM %Dictionary.IndexDefinition
|
|
301
|
+
WHERE parent = ? AND name = ?
|
|
302
|
+
""",
|
|
303
|
+
[f"{schema}.{table}", index_name],
|
|
304
|
+
)
|
|
305
|
+
else:
|
|
306
|
+
# Check for index globally
|
|
307
|
+
cursor.execute(
|
|
308
|
+
"""
|
|
309
|
+
SELECT COUNT(*)
|
|
310
|
+
FROM %Dictionary.IndexDefinition
|
|
311
|
+
WHERE name = ?
|
|
312
|
+
""",
|
|
313
|
+
[index_name],
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
result = cursor.fetchone()
|
|
317
|
+
return result[0] > 0
|
|
318
|
+
|
|
319
|
+
except Exception as e:
|
|
320
|
+
logger.debug(f"Could not check index existence for {index_name}: {e}")
|
|
321
|
+
# Fallback: assume index doesn't exist
|
|
322
|
+
return False
|
|
323
|
+
finally:
|
|
324
|
+
# Ensure cleanup of temporary connection and cursor
|
|
325
|
+
if cursor:
|
|
326
|
+
try:
|
|
327
|
+
cursor.close()
|
|
328
|
+
except:
|
|
329
|
+
pass
|
|
330
|
+
if temp_connection:
|
|
331
|
+
try:
|
|
332
|
+
temp_connection.close()
|
|
333
|
+
except:
|
|
334
|
+
pass
|
|
335
|
+
|
|
336
|
+
@staticmethod
|
|
337
|
+
def _check_index_exists_with_connection(
|
|
338
|
+
connection: Any, index_name: str, table_name: Optional[str] = None
|
|
339
|
+
) -> bool:
|
|
340
|
+
"""
|
|
341
|
+
Fallback method using provided connection for index existence check.
|
|
342
|
+
"""
|
|
343
|
+
try:
|
|
344
|
+
cursor = connection.cursor()
|
|
345
|
+
|
|
346
|
+
# IRIS doesn't have INFORMATION_SCHEMA.STATISTICS, use system tables
|
|
347
|
+
if table_name:
|
|
348
|
+
# Split schema and table name
|
|
349
|
+
if "." in table_name:
|
|
350
|
+
schema, table = table_name.split(".", 1)
|
|
351
|
+
else:
|
|
352
|
+
schema = "USER"
|
|
353
|
+
table = table_name
|
|
354
|
+
|
|
355
|
+
# Check for index on specific table
|
|
356
|
+
cursor.execute(
|
|
357
|
+
"""
|
|
358
|
+
SELECT COUNT(*)
|
|
359
|
+
FROM %Dictionary.IndexDefinition
|
|
360
|
+
WHERE parent = ? AND name = ?
|
|
361
|
+
""",
|
|
362
|
+
[f"{schema}.{table}", index_name],
|
|
363
|
+
)
|
|
364
|
+
else:
|
|
365
|
+
# Check for index globally
|
|
366
|
+
cursor.execute(
|
|
367
|
+
"""
|
|
368
|
+
SELECT COUNT(*)
|
|
369
|
+
FROM %Dictionary.IndexDefinition
|
|
370
|
+
WHERE name = ?
|
|
371
|
+
""",
|
|
372
|
+
[index_name],
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
result = cursor.fetchone()
|
|
376
|
+
cursor.close()
|
|
377
|
+
return result[0] > 0
|
|
378
|
+
|
|
379
|
+
except Exception as e:
|
|
380
|
+
logger.debug(f"Could not check index existence for {index_name}: {e}")
|
|
381
|
+
# Fallback: assume index doesn't exist
|
|
382
|
+
return False
|
|
383
|
+
|
|
384
|
+
@staticmethod
|
|
385
|
+
def get_table_columns(connection: Any, table_name: str) -> List[Dict[str, Any]]:
|
|
386
|
+
"""
|
|
387
|
+
Get column information for a table using IRIS-compatible syntax.
|
|
388
|
+
Uses a self-managed database connection to avoid "DataRow is inaccessible" errors.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
connection: Database connection (kept for compatibility, but not used)
|
|
392
|
+
table_name: Name of the table (schema.table format)
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
List of dictionaries with column information
|
|
396
|
+
"""
|
|
397
|
+
# Use self-managed connection to avoid interference with other operations
|
|
398
|
+
if get_iris_dbapi_connection is None:
|
|
399
|
+
logger.warning(
|
|
400
|
+
"Self-managed connections not available, falling back to provided connection"
|
|
401
|
+
)
|
|
402
|
+
return IRISSQLUtils._get_table_columns_with_connection(
|
|
403
|
+
connection, table_name
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
temp_connection = None
|
|
407
|
+
cursor = None
|
|
408
|
+
try:
|
|
409
|
+
temp_connection = get_iris_dbapi_connection()
|
|
410
|
+
if not temp_connection:
|
|
411
|
+
logger.error(
|
|
412
|
+
"Failed to obtain temporary connection for table columns check"
|
|
413
|
+
)
|
|
414
|
+
return []
|
|
415
|
+
|
|
416
|
+
# Split schema and table name
|
|
417
|
+
if "." in table_name:
|
|
418
|
+
schema, table = table_name.split(".", 1)
|
|
419
|
+
else:
|
|
420
|
+
schema = "USER"
|
|
421
|
+
table = table_name
|
|
422
|
+
|
|
423
|
+
cursor = temp_connection.cursor()
|
|
424
|
+
cursor.execute(
|
|
425
|
+
"""
|
|
426
|
+
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT
|
|
427
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
428
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
429
|
+
ORDER BY ORDINAL_POSITION
|
|
430
|
+
""",
|
|
431
|
+
[schema, table],
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
columns = []
|
|
435
|
+
for row in cursor.fetchall():
|
|
436
|
+
columns.append(
|
|
437
|
+
{
|
|
438
|
+
"name": row[0],
|
|
439
|
+
"type": row[1],
|
|
440
|
+
"nullable": row[2],
|
|
441
|
+
"default": row[3],
|
|
442
|
+
}
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
return columns
|
|
446
|
+
|
|
447
|
+
except Exception as e:
|
|
448
|
+
logger.error(f"Failed to get columns for {table_name}: {e}")
|
|
449
|
+
return []
|
|
450
|
+
finally:
|
|
451
|
+
# Ensure cleanup of temporary connection and cursor
|
|
452
|
+
if cursor:
|
|
453
|
+
try:
|
|
454
|
+
cursor.close()
|
|
455
|
+
except:
|
|
456
|
+
pass
|
|
457
|
+
if temp_connection:
|
|
458
|
+
try:
|
|
459
|
+
temp_connection.close()
|
|
460
|
+
except:
|
|
461
|
+
pass
|
|
462
|
+
|
|
463
|
+
@staticmethod
|
|
464
|
+
def _get_table_columns_with_connection(
|
|
465
|
+
connection: Any, table_name: str
|
|
466
|
+
) -> List[Dict[str, Any]]:
|
|
467
|
+
"""
|
|
468
|
+
Fallback method using provided connection for table columns check.
|
|
469
|
+
"""
|
|
470
|
+
try:
|
|
471
|
+
# Split schema and table name
|
|
472
|
+
if "." in table_name:
|
|
473
|
+
schema, table = table_name.split(".", 1)
|
|
474
|
+
else:
|
|
475
|
+
schema = "USER"
|
|
476
|
+
table = table_name
|
|
477
|
+
|
|
478
|
+
cursor = connection.cursor()
|
|
479
|
+
cursor.execute(
|
|
480
|
+
"""
|
|
481
|
+
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT
|
|
482
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
483
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
484
|
+
ORDER BY ORDINAL_POSITION
|
|
485
|
+
""",
|
|
486
|
+
[schema, table],
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
columns = []
|
|
490
|
+
for row in cursor.fetchall():
|
|
491
|
+
columns.append(
|
|
492
|
+
{
|
|
493
|
+
"name": row[0],
|
|
494
|
+
"type": row[1],
|
|
495
|
+
"nullable": row[2],
|
|
496
|
+
"default": row[3],
|
|
497
|
+
}
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
cursor.close()
|
|
501
|
+
return columns
|
|
502
|
+
|
|
503
|
+
except Exception as e:
|
|
504
|
+
logger.error(f"Failed to get columns for {table_name}: {e}")
|
|
505
|
+
return []
|
|
506
|
+
|
|
507
|
+
@staticmethod
|
|
508
|
+
def optimize_vector_table(
|
|
509
|
+
connection: Any,
|
|
510
|
+
table_name: str,
|
|
511
|
+
vector_column: str = "embedding",
|
|
512
|
+
vector_config: dict = None,
|
|
513
|
+
) -> bool:
|
|
514
|
+
"""
|
|
515
|
+
Optimize a table for vector operations by creating appropriate indices.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
connection: Database connection
|
|
519
|
+
table_name: Name of the table
|
|
520
|
+
vector_column: Name of the vector column
|
|
521
|
+
vector_config: Vector index configuration with HNSW parameters
|
|
522
|
+
|
|
523
|
+
Returns:
|
|
524
|
+
True if successful, False otherwise
|
|
525
|
+
"""
|
|
526
|
+
try:
|
|
527
|
+
logger.info(f"Optimizing table {table_name} for vector operations...")
|
|
528
|
+
|
|
529
|
+
# Get vector index configuration with defaults
|
|
530
|
+
if vector_config is None:
|
|
531
|
+
vector_config = {
|
|
532
|
+
"type": "HNSW",
|
|
533
|
+
"M": 16,
|
|
534
|
+
"efConstruction": 200,
|
|
535
|
+
"Distance": "COSINE",
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
# Create vector index for similarity search with configurable parameters
|
|
539
|
+
vector_success = IRISSQLUtils.create_vector_index_with_config(
|
|
540
|
+
connection, table_name, vector_column, vector_config
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
# Create simple indices on commonly queried columns
|
|
544
|
+
common_columns = ["doc_id", "id", "source_doc_id"]
|
|
545
|
+
for col in common_columns:
|
|
546
|
+
# Check if column exists first
|
|
547
|
+
columns = IRISSQLUtils.get_table_columns(connection, table_name)
|
|
548
|
+
column_names = [c["name"].lower() for c in columns]
|
|
549
|
+
|
|
550
|
+
if col.lower() in column_names:
|
|
551
|
+
IRISSQLUtils.create_simple_index(connection, table_name, col)
|
|
552
|
+
|
|
553
|
+
return vector_success
|
|
554
|
+
|
|
555
|
+
except Exception as e:
|
|
556
|
+
logger.error(f"Failed to optimize table {table_name}: {e}")
|
|
557
|
+
return False
|
|
558
|
+
|
|
559
|
+
@staticmethod
|
|
560
|
+
def create_vector_index_with_config(
|
|
561
|
+
connection: Any,
|
|
562
|
+
table_name: str,
|
|
563
|
+
column_name: str,
|
|
564
|
+
vector_config: dict,
|
|
565
|
+
index_name: Optional[str] = None,
|
|
566
|
+
) -> bool:
|
|
567
|
+
"""
|
|
568
|
+
Create a vector index using configurable HNSW parameters.
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
connection: Database connection
|
|
572
|
+
table_name: Name of the table
|
|
573
|
+
column_name: Name of the vector column
|
|
574
|
+
vector_config: Vector index configuration with HNSW parameters
|
|
575
|
+
index_name: Optional custom index name
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
True if successful, False otherwise
|
|
579
|
+
"""
|
|
580
|
+
if not index_name:
|
|
581
|
+
# Clean table name for index naming
|
|
582
|
+
clean_table = table_name.replace(".", "_")
|
|
583
|
+
index_name = f"{clean_table}_{column_name}_vector_idx"
|
|
584
|
+
|
|
585
|
+
try:
|
|
586
|
+
# Build HNSW parameters string
|
|
587
|
+
hnsw_params = []
|
|
588
|
+
if "M" in vector_config:
|
|
589
|
+
hnsw_params.append(f"M={vector_config['M']}")
|
|
590
|
+
if "efConstruction" in vector_config:
|
|
591
|
+
hnsw_params.append(f"efConstruction={vector_config['efConstruction']}")
|
|
592
|
+
if "Distance" in vector_config:
|
|
593
|
+
hnsw_params.append(f"Distance='{vector_config['Distance']}'")
|
|
594
|
+
|
|
595
|
+
params_str = ", ".join(hnsw_params)
|
|
596
|
+
|
|
597
|
+
# Create HNSW vector index with proper parameters
|
|
598
|
+
# Note: IRIS doesn't support "IF NOT EXISTS" in CREATE INDEX statements
|
|
599
|
+
if params_str:
|
|
600
|
+
sql = f"""
|
|
601
|
+
CREATE INDEX {index_name}
|
|
602
|
+
ON {table_name} ({column_name})
|
|
603
|
+
AS HNSW({params_str})
|
|
604
|
+
"""
|
|
605
|
+
else:
|
|
606
|
+
sql = f"""
|
|
607
|
+
CREATE INDEX {index_name}
|
|
608
|
+
ON {table_name} ({column_name})
|
|
609
|
+
AS HNSW(M=16, efConstruction=200, Distance='COSINE')
|
|
610
|
+
"""
|
|
611
|
+
|
|
612
|
+
# Check if index already exists first
|
|
613
|
+
if IRISSQLUtils.check_index_exists(connection, index_name, table_name):
|
|
614
|
+
logger.debug(f"Vector index {index_name} already exists")
|
|
615
|
+
return True
|
|
616
|
+
|
|
617
|
+
cursor = connection.cursor()
|
|
618
|
+
cursor.execute(sql)
|
|
619
|
+
connection.commit()
|
|
620
|
+
cursor.close()
|
|
621
|
+
logger.info(
|
|
622
|
+
f"✓ Created vector index {index_name} on {table_name}.{column_name} with parameters: {params_str}"
|
|
623
|
+
)
|
|
624
|
+
return True
|
|
625
|
+
|
|
626
|
+
except Exception as e:
|
|
627
|
+
error_msg = str(e).lower()
|
|
628
|
+
# Handle IRIS-specific error codes and messages
|
|
629
|
+
if (
|
|
630
|
+
"already exists" in error_msg
|
|
631
|
+
or "duplicate" in error_msg
|
|
632
|
+
or "sqlcode: <-324>" in error_msg
|
|
633
|
+
or "index with this name already defined" in error_msg
|
|
634
|
+
):
|
|
635
|
+
logger.debug(f"Vector index {index_name} already exists")
|
|
636
|
+
return True
|
|
637
|
+
else:
|
|
638
|
+
logger.error(f"Failed to create vector index {index_name}: {e}")
|
|
639
|
+
return False
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def test_iris_sql_utils():
|
|
643
|
+
"""Test the IRIS SQL utilities with self-managed connections."""
|
|
644
|
+
print("=== Testing IRIS SQL Utilities ===")
|
|
645
|
+
|
|
646
|
+
try:
|
|
647
|
+
# Test with self-managed connections (pass None as connection since functions will create their own)
|
|
648
|
+
utils = IRISSQLUtils()
|
|
649
|
+
|
|
650
|
+
# Test table existence check
|
|
651
|
+
exists = utils.check_table_exists(None, "RAG.SourceDocuments")
|
|
652
|
+
print(f"✓ Table existence check: {exists}")
|
|
653
|
+
|
|
654
|
+
# Test column information
|
|
655
|
+
columns = utils.get_table_columns(None, "RAG.SourceDocuments")
|
|
656
|
+
print(f"✓ Found {len(columns)} columns")
|
|
657
|
+
|
|
658
|
+
# Test index existence check
|
|
659
|
+
index_exists = utils.check_index_exists(
|
|
660
|
+
None, "RAG_SourceDocuments_embedding_vector_idx", "RAG.SourceDocuments"
|
|
661
|
+
)
|
|
662
|
+
print(f"✓ Index existence check: {index_exists}")
|
|
663
|
+
|
|
664
|
+
# For index creation, we still need a connection since it performs DDL operations
|
|
665
|
+
if get_iris_dbapi_connection:
|
|
666
|
+
connection = get_iris_dbapi_connection()
|
|
667
|
+
if connection:
|
|
668
|
+
# Test vector index creation (this should work with proper syntax)
|
|
669
|
+
print("Testing vector index creation...")
|
|
670
|
+
success = utils.create_vector_index(
|
|
671
|
+
connection,
|
|
672
|
+
"RAG.SourceDocuments",
|
|
673
|
+
"embedding",
|
|
674
|
+
"test_vector_idx",
|
|
675
|
+
"COSINE",
|
|
676
|
+
"HNSW",
|
|
677
|
+
)
|
|
678
|
+
print(f"✓ Vector index creation: {success}")
|
|
679
|
+
|
|
680
|
+
# Clean up test index
|
|
681
|
+
if success:
|
|
682
|
+
try:
|
|
683
|
+
cursor = connection.cursor()
|
|
684
|
+
cursor.execute("DROP INDEX test_vector_idx")
|
|
685
|
+
cursor.close()
|
|
686
|
+
print("✓ Test index cleaned up")
|
|
687
|
+
except:
|
|
688
|
+
pass
|
|
689
|
+
|
|
690
|
+
connection.close()
|
|
691
|
+
else:
|
|
692
|
+
print("⚠️ Could not get database connection for index creation test")
|
|
693
|
+
else:
|
|
694
|
+
print("⚠️ Self-managed connections not available for index creation test")
|
|
695
|
+
|
|
696
|
+
return True
|
|
697
|
+
|
|
698
|
+
except Exception as e:
|
|
699
|
+
print(f"❌ Test failed: {e}")
|
|
700
|
+
return False
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
if __name__ == "__main__":
|
|
704
|
+
test_iris_sql_utils()
|