iris-pgwire 1.5.0__tar.gz → 1.5.2__tar.gz
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.
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/PKG-INFO +1 -1
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/__init__.py +1 -1
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/backend_selector.py +1 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/bulk_executor.py +28 -11
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/dbapi_executor.py +23 -2
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/iris_executor.py +42 -4
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/models/backend_config.py +13 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/server.py +4 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/.gitignore +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/LICENSE +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/README.md +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/pyproject.toml +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/_column_naming.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/_noop_cursor.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/_result_processing.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/_type_mapping.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/auth/__init__.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/auth/auth_selector.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/auth/gssapi_auth.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/auth/oauth_bridge.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/auth/scram.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/auth/wallet_credentials.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/catalog/__init__.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/catalog/catalog_functions.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/catalog/catalog_router.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/catalog/oid_generator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/catalog/pg_attrdef.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/catalog/pg_attribute.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/catalog/pg_class.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/catalog/pg_constraint.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/catalog/pg_index.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/catalog/pg_namespace.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/catalog/pg_type.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/column_validator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/config.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/config_schema.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/constitutional.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/conversions/__init__.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/conversions/bulk_insert.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/conversions/date_horolog.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/conversions/ddl_idempotency.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/conversions/ddl_splitter.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/conversions/json_path.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/conversions/vector_syntax.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/copy_handler.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/csv_processor.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/dbapi_connection_pool.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/debug_tracer.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/health_checker.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/integratedml.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/iris_constructs.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/iris_log_handler.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/iris_user_management.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/migrations/__init__.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/migrations/__main__.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/migrations/executor.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/models/__init__.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/models/connection_pool_state.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/models/dbapi_connection.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/models/ipm_metadata.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/models/vector_query_request.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/observability.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/performance_monitor.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/protocol.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/quality/__init__.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/quality/__main__.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/quality/code_quality_validator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/quality/documentation_validator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/quality/package_metadata_validator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/quality/security_validator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/quality/validator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/schema_mapper.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/__init__.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/alias_extractor.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/api.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/boolean_translator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/cache.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/confidence_analyzer.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/config.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/constraint_translator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/copy_parser.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/date_translator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/ddl_parser.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/ddl_translator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/debug.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/default_values.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/enum_registry.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/enum_translator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/error_handler.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/identifier_normalizer.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/index_validator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/interceptor.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/logging_config.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/mappings/__init__.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/mappings/constructs.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/mappings/datatypes.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/mappings/document_filters.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/mappings/functions.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/metadata_cache.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/metrics.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/models.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/normalizer.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/parser.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/performance_monitor.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/pipeline.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/refiner.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/reserved_words.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/returning_plan.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/skipped_table_set.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/statement_filter.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/transaction_translator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/translator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/type_translator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/validator.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/testing/__init__.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/testing/base_fixture_builder.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/type_mapping.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/vector_metrics.py +0 -0
- {iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/vector_optimizer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iris-pgwire
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.2
|
|
4
4
|
Summary: PostgreSQL Wire Protocol Server for InterSystems IRIS - Connect BI tools, Python frameworks, and PostgreSQL clients to IRIS databases
|
|
5
5
|
Project-URL: Homepage, https://github.com/intersystems-community/iris-pgwire
|
|
6
6
|
Project-URL: Documentation, https://github.com/intersystems-community/iris-pgwire#readme
|
|
@@ -6,7 +6,7 @@ Based on the specification from docs/iris_pgwire_plan.md and proven patterns fro
|
|
|
6
6
|
caretdev/sqlalchemy-iris.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
__version__ = "1.5.
|
|
9
|
+
__version__ = "1.5.2"
|
|
10
10
|
__author__ = "Thomas Dyar <thomas.dyar@intersystems.com>"
|
|
11
11
|
|
|
12
12
|
# Don't import server/protocol in __init__ to avoid sys.modules conflicts
|
|
@@ -265,6 +265,7 @@ class BackendSelector:
|
|
|
265
265
|
connection_pool_size=config.pool_size,
|
|
266
266
|
connection_pool_timeout=float(config.pool_timeout),
|
|
267
267
|
strict_single_connection=config.strict_single_connection,
|
|
268
|
+
query_timeout=config.query_timeout,
|
|
268
269
|
)
|
|
269
270
|
# backend_type is now set in IRISExecutor.__init__
|
|
270
271
|
logger.info(
|
|
@@ -71,10 +71,23 @@ class BulkExecutor:
|
|
|
71
71
|
total_rows = 0
|
|
72
72
|
batch = []
|
|
73
73
|
actual_column_names = column_names
|
|
74
|
+
# Lazily resolved from schema when CSV has no header and no column list
|
|
75
|
+
schema_columns: list[str] | None = None
|
|
74
76
|
|
|
75
77
|
async for row_dict in rows:
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
+
# When column_names is None and the CSV has no header, the CSV processor
|
|
79
|
+
# generates placeholder keys (column_0, column_1, …). Detect this on
|
|
80
|
+
# the first row, fetch real column names from IRIS schema, and re-map.
|
|
81
|
+
first_key = next(iter(row_dict), None)
|
|
82
|
+
if first_key is not None and first_key.startswith("column_"):
|
|
83
|
+
if schema_columns is None:
|
|
84
|
+
schema_columns = await self.get_table_columns(table_name)
|
|
85
|
+
logger.debug(f"Columns fetched from schema for {table_name}: {schema_columns}")
|
|
86
|
+
if schema_columns:
|
|
87
|
+
actual_column_names = schema_columns
|
|
88
|
+
values = list(row_dict.values())
|
|
89
|
+
row_dict = dict(zip(schema_columns, values))
|
|
90
|
+
elif actual_column_names is None:
|
|
78
91
|
actual_column_names = list(row_dict.keys())
|
|
79
92
|
logger.debug(f"Columns inferred from data: {actual_column_names}")
|
|
80
93
|
|
|
@@ -321,25 +334,29 @@ class BulkExecutor:
|
|
|
321
334
|
"""
|
|
322
335
|
Get column names for a table using INFORMATION_SCHEMA.
|
|
323
336
|
|
|
337
|
+
Bypasses the catalog router (which intercepts INFORMATION_SCHEMA queries
|
|
338
|
+
for pg_catalog emulation) by calling _execute_external_async directly.
|
|
339
|
+
|
|
324
340
|
Args:
|
|
325
341
|
table_name: Table name
|
|
326
342
|
|
|
327
343
|
Returns:
|
|
328
|
-
List of column names
|
|
344
|
+
List of column names in ordinal order
|
|
329
345
|
|
|
330
346
|
Raises:
|
|
331
347
|
Exception: IRIS query error
|
|
332
348
|
"""
|
|
333
|
-
query =
|
|
334
|
-
SELECT
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
"""
|
|
349
|
+
query = (
|
|
350
|
+
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS "
|
|
351
|
+
f"WHERE LOWER(TABLE_NAME) = LOWER('{table_name}') "
|
|
352
|
+
"ORDER BY ORDINAL_POSITION"
|
|
353
|
+
)
|
|
339
354
|
|
|
340
|
-
|
|
355
|
+
# Bypass iris_executor.execute_query (which routes through the catalog
|
|
356
|
+
# router and returns empty rows for INFORMATION_SCHEMA queries) by
|
|
357
|
+
# calling the external execution path directly.
|
|
358
|
+
result = await self.iris_executor._execute_external_async(query, [])
|
|
341
359
|
|
|
342
|
-
# Extract column names from result
|
|
343
360
|
columns = []
|
|
344
361
|
if result and "rows" in result:
|
|
345
362
|
columns = [row[0] for row in result["rows"]]
|
|
@@ -202,7 +202,25 @@ class DBAPIExecutor:
|
|
|
202
202
|
conn_wrapper.connection, plan, converted_params, session_id, original_sql
|
|
203
203
|
)
|
|
204
204
|
|
|
205
|
-
|
|
205
|
+
query_timeout = getattr(self.config, "query_timeout", 30.0)
|
|
206
|
+
try:
|
|
207
|
+
rows, columns, row_count = await asyncio.wait_for(
|
|
208
|
+
asyncio.to_thread(execute_in_thread), timeout=query_timeout
|
|
209
|
+
)
|
|
210
|
+
except TimeoutError:
|
|
211
|
+
logger.error(
|
|
212
|
+
"Query timed out — evicting connection from pool",
|
|
213
|
+
sql_preview=sql[:120],
|
|
214
|
+
timeout_seconds=query_timeout,
|
|
215
|
+
session_id=session_id,
|
|
216
|
+
)
|
|
217
|
+
# Mark connection unhealthy so it gets evicted, not recycled
|
|
218
|
+
if conn_wrapper:
|
|
219
|
+
conn_wrapper.is_healthy = False
|
|
220
|
+
raise RuntimeError(
|
|
221
|
+
f"Query execution timed out after {query_timeout}s. "
|
|
222
|
+
"The IRIS connection is being evicted to prevent pool exhaustion."
|
|
223
|
+
)
|
|
206
224
|
|
|
207
225
|
tx_sql_upper = original_sql.strip().upper()
|
|
208
226
|
self._update_transaction_state(session_id, tx_sql_upper)
|
|
@@ -1044,7 +1062,10 @@ class DBAPIExecutor:
|
|
|
1044
1062
|
for i, col in enumerate(columns):
|
|
1045
1063
|
if col in ("id", "uuid", "_id"):
|
|
1046
1064
|
if params and len(params) > i:
|
|
1047
|
-
|
|
1065
|
+
# Return UPPERCASE so the WHERE clause matches IRIS's stored column name.
|
|
1066
|
+
# IRIS stores unquoted column names as uppercase; using "id" (lowercase)
|
|
1067
|
+
# in a quoted identifier fails silently and returns 0 rows.
|
|
1068
|
+
return col.upper(), params[i]
|
|
1048
1069
|
return None, None
|
|
1049
1070
|
|
|
1050
1071
|
return None, None
|
|
@@ -100,6 +100,7 @@ class IRISExecutor:
|
|
|
100
100
|
enable_query_cache: bool = True,
|
|
101
101
|
query_cache_size: int = 1000,
|
|
102
102
|
strict_single_connection: bool = False,
|
|
103
|
+
query_timeout: float = 30.0,
|
|
103
104
|
):
|
|
104
105
|
self.iris_config = iris_config
|
|
105
106
|
self.server = server # Reference to server for P4 cancellation
|
|
@@ -108,6 +109,7 @@ class IRISExecutor:
|
|
|
108
109
|
self.enable_query_cache = enable_query_cache
|
|
109
110
|
self.query_cache_size = query_cache_size
|
|
110
111
|
self.strict_single_connection = strict_single_connection
|
|
112
|
+
self.query_timeout = query_timeout
|
|
111
113
|
|
|
112
114
|
self.connection = None
|
|
113
115
|
self.session_connections = {}
|
|
@@ -1763,7 +1765,10 @@ class IRISExecutor:
|
|
|
1763
1765
|
for i, col in enumerate(columns):
|
|
1764
1766
|
if col in id_col_names:
|
|
1765
1767
|
id_col_idx = i
|
|
1766
|
-
|
|
1768
|
+
# Return UPPERCASE so the WHERE clause matches IRIS's stored column name.
|
|
1769
|
+
# IRIS stores unquoted column names as uppercase; using "id" (lowercase)
|
|
1770
|
+
# in a quoted identifier fails silently and returns 0 rows.
|
|
1771
|
+
id_col_name = col.upper()
|
|
1767
1772
|
break
|
|
1768
1773
|
|
|
1769
1774
|
if id_col_idx is None:
|
|
@@ -2884,6 +2889,12 @@ class IRISExecutor:
|
|
|
2884
2889
|
Execute SQL using external IRIS connection with proper async threading
|
|
2885
2890
|
"""
|
|
2886
2891
|
|
|
2892
|
+
import threading
|
|
2893
|
+
|
|
2894
|
+
# Shared flag: asyncio side sets this True when wait_for times out so the
|
|
2895
|
+
# sync thread knows to close (evict) the connection instead of recycling it.
|
|
2896
|
+
timed_out = threading.Event()
|
|
2897
|
+
|
|
2887
2898
|
def _sync_external_execute(captured_sql, captured_params, captured_session_id):
|
|
2888
2899
|
"""Synchronous external IRIS execution in thread pool"""
|
|
2889
2900
|
sql = captured_sql
|
|
@@ -3103,13 +3114,40 @@ class IRISExecutor:
|
|
|
3103
3114
|
except Exception:
|
|
3104
3115
|
pass
|
|
3105
3116
|
if conn:
|
|
3106
|
-
|
|
3117
|
+
if timed_out.is_set():
|
|
3118
|
+
# Evict — don't return a lock-held connection to the pool
|
|
3119
|
+
try:
|
|
3120
|
+
conn.close()
|
|
3121
|
+
except Exception:
|
|
3122
|
+
pass
|
|
3123
|
+
logger.warning(
|
|
3124
|
+
"Evicted timed-out IRIS connection from pool",
|
|
3125
|
+
session_id=session_id,
|
|
3126
|
+
)
|
|
3127
|
+
else:
|
|
3128
|
+
self._return_connection(conn, session_id=session_id)
|
|
3107
3129
|
|
|
3108
|
-
# Execute in thread pool to avoid blocking event loop
|
|
3130
|
+
# Execute in thread pool to avoid blocking event loop.
|
|
3131
|
+
# Wrap with a per-query timeout to prevent lock-wait cascades from
|
|
3132
|
+
# exhausting the connection pool (PGWIRE_QUERY_TIMEOUT, default 30s).
|
|
3109
3133
|
loop = asyncio.get_event_loop()
|
|
3110
|
-
|
|
3134
|
+
future = loop.run_in_executor(
|
|
3111
3135
|
self._get_executor(session_id), _sync_external_execute, sql, params, session_id
|
|
3112
3136
|
)
|
|
3137
|
+
try:
|
|
3138
|
+
return await asyncio.wait_for(future, timeout=self.query_timeout)
|
|
3139
|
+
except TimeoutError:
|
|
3140
|
+
timed_out.set() # Signal the sync thread to evict the connection
|
|
3141
|
+
logger.error(
|
|
3142
|
+
"Query timed out — connection will be evicted from pool",
|
|
3143
|
+
sql_preview=sql[:120],
|
|
3144
|
+
timeout_seconds=self.query_timeout,
|
|
3145
|
+
session_id=session_id,
|
|
3146
|
+
)
|
|
3147
|
+
raise RuntimeError(
|
|
3148
|
+
f"Query execution timed out after {self.query_timeout}s. "
|
|
3149
|
+
"The IRIS connection is being evicted to prevent pool exhaustion."
|
|
3150
|
+
)
|
|
3113
3151
|
|
|
3114
3152
|
def _get_iris_connection(self):
|
|
3115
3153
|
"""
|
|
@@ -80,6 +80,18 @@ class BackendConfig(BaseModel):
|
|
|
80
80
|
description="Connection lifetime in seconds (1 hour default)",
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
+
# Per-query execution timeout (separate from pool wait timeout)
|
|
84
|
+
query_timeout: float = Field(
|
|
85
|
+
default=30.0,
|
|
86
|
+
ge=1.0,
|
|
87
|
+
le=600.0,
|
|
88
|
+
description=(
|
|
89
|
+
"Maximum seconds a single query may execute before being cancelled. "
|
|
90
|
+
"Prevents lock-wait cascades from exhausting the connection pool. "
|
|
91
|
+
"Set via PGWIRE_QUERY_TIMEOUT env var."
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
|
|
83
95
|
# Protocol Behavior
|
|
84
96
|
strict_single_connection: bool = Field(
|
|
85
97
|
default=False,
|
|
@@ -205,6 +217,7 @@ class BackendConfig(BaseModel):
|
|
|
205
217
|
("PGWIRE_POOL_MAX_OVERFLOW", "pool_max_overflow", int),
|
|
206
218
|
("PGWIRE_POOL_TIMEOUT", "pool_timeout", int),
|
|
207
219
|
("PGWIRE_POOL_RECYCLE", "pool_recycle", int),
|
|
220
|
+
("PGWIRE_QUERY_TIMEOUT", "query_timeout", float),
|
|
208
221
|
# Observability
|
|
209
222
|
("PGWIRE_ENABLE_OTEL", "enable_otel", _as_bool),
|
|
210
223
|
("PGWIRE_OTEL_ENDPOINT", "otel_endpoint", None),
|
|
@@ -75,6 +75,7 @@ class PGWireServer:
|
|
|
75
75
|
enable_scram: bool = False,
|
|
76
76
|
connection_pool_size: int = 10,
|
|
77
77
|
connection_pool_timeout: float = 5.0,
|
|
78
|
+
query_timeout: float = 30.0,
|
|
78
79
|
enable_query_cache: bool = True,
|
|
79
80
|
query_cache_size: int = 1000,
|
|
80
81
|
backend_type: str | None = None,
|
|
@@ -122,6 +123,7 @@ class PGWireServer:
|
|
|
122
123
|
iris_namespace=iris_namespace,
|
|
123
124
|
pool_size=connection_pool_size,
|
|
124
125
|
pool_timeout=int(connection_pool_timeout),
|
|
126
|
+
query_timeout=query_timeout,
|
|
125
127
|
strict_single_connection=strict_single_connection,
|
|
126
128
|
)
|
|
127
129
|
|
|
@@ -300,6 +302,7 @@ def _gather_main_config() -> dict[str, Any]:
|
|
|
300
302
|
"ssl_key_path": os.getenv("PGWIRE_SSL_KEY"),
|
|
301
303
|
"connection_pool_size": int(os.getenv("PGWIRE_POOL_SIZE", "10")),
|
|
302
304
|
"connection_pool_timeout": float(os.getenv("PGWIRE_POOL_TIMEOUT", "5.0")),
|
|
305
|
+
"query_timeout": float(os.getenv("PGWIRE_QUERY_TIMEOUT", "30.0")),
|
|
303
306
|
"enable_query_cache": os.getenv("PGWIRE_QUERY_CACHE_ENABLED", "true").lower() == "true",
|
|
304
307
|
"query_cache_size": int(os.getenv("PGWIRE_QUERY_CACHE_SIZE", "1000")),
|
|
305
308
|
"debug": os.getenv("PGWIRE_DEBUG", "false").lower() == "true",
|
|
@@ -327,6 +330,7 @@ async def main():
|
|
|
327
330
|
ssl_key_path=config["ssl_key_path"],
|
|
328
331
|
connection_pool_size=config["connection_pool_size"],
|
|
329
332
|
connection_pool_timeout=config["connection_pool_timeout"],
|
|
333
|
+
query_timeout=config["query_timeout"],
|
|
330
334
|
enable_query_cache=config["enable_query_cache"],
|
|
331
335
|
query_cache_size=config["query_cache_size"],
|
|
332
336
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/quality/package_metadata_validator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/boolean_translator.py
RENAMED
|
File without changes
|
|
File without changes
|
{iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/confidence_analyzer.py
RENAMED
|
File without changes
|
|
File without changes
|
{iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/constraint_translator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/identifier_normalizer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/mappings/constructs.py
RENAMED
|
File without changes
|
{iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/mappings/datatypes.py
RENAMED
|
File without changes
|
{iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/mappings/document_filters.py
RENAMED
|
File without changes
|
{iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/mappings/functions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/performance_monitor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iris_pgwire-1.5.0 → iris_pgwire-1.5.2}/src/iris_pgwire/sql_translator/transaction_translator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|