Skip to content

Commit 044561a

Browse files
committed
Robust schema mapping and technical reference guide (v1.1.0)
1 parent bbd8ee0 commit 044561a

File tree

6 files changed

+82
-30
lines changed

6 files changed

+82
-30
lines changed

AGENTS.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,36 @@ Python 3.11+: Follow standard conventions
3636

3737

3838
<!-- MANUAL ADDITIONS START -->
39+
40+
## IRIS Technical Reference (Critical for DBAPI & Embedded)
41+
42+
### 1. DBAPI Connection Pattern (intersystems-irispython)
43+
Always use this robust import pattern to obtain the DBAPI module. This handles different package versions and environment quirks.
44+
```python
45+
try:
46+
import iris.dbapi as iris_dbapi # Modern/Standard
47+
except ImportError:
48+
try:
49+
import intersystems_iris.dbapi._DBAPI as iris_dbapi # Deep Fallback
50+
except ImportError:
51+
# Last resort: check if iris module itself has connect (older versions)
52+
import iris as iris_dbapi
53+
```
54+
55+
### 2. Embedded SQL Execution (iris.sql.exec)
56+
When running code inside IRIS (Embedded Python), parameters **MUST** be passed using the splat operator `*params` to be treated as positional arguments.
57+
```python
58+
# CORRECT
59+
iris.sql.exec(sql, *params)
60+
61+
# INCORRECT (passes list as a single argument)
62+
iris.sql.exec(sql, params)
63+
```
64+
65+
### 3. Case Sensitivity & Identifiers
66+
- **Schema Name**: Always use `SQLUser` (exact casing). IRIS package/schema names are case-sensitive.
67+
- **Quoted Identifiers**: Identifiers in double quotes (e.g., `"workflow"`) are case-sensitive in IRIS.
68+
- **Unquoted Identifiers**: Automatically mapped to UPPERCASE by IRIS.
69+
- **Normalization**: To ensure consistency, the normalizer should preserve the casing of quoted identifiers and map unquoted ones to uppercase, but it **MUST NOT** change the case of the `SQLUser` schema prefix.
70+
3971
<!-- MANUAL ADDITIONS END -->

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.1.0] - 2026-01-17
9+
10+
### Added
11+
- **IRIS Technical Reference**: Added definitive guide to `AGENTS.md` covering DBAPI connection patterns, embedded SQL parameter passing, and case-sensitivity rules.
12+
13+
### Fixed
14+
- **Robust Schema Mapping**: Rewrote `translate_input_schema` to correctly preserve table name quoting and casing (e.g., `public."workflow"` -> `SQLUser."workflow"`). This resolves "Class not found" errors in IRIS when ORMs use quoted identifiers.
15+
- **Embedded Parameter Passing**: Ensured `iris.sql.exec` receives parameters as positional arguments using the splat operator (`*params`) in all execution paths.
16+
- **Redundant Translation Cleanup**: Removed conflicting schema translation regexes in `iris_executor.py` that were previously stripping quotes from identifiers.
17+
818
## [1.0.9] - 2026-01-17
919

1020
### Fixed

list_iris_tables.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import iris
2+
3+
4+
def list_sqluser_tables():
5+
print("Listing classes in SQLUser package:")
6+
# Query %Dictionary.ClassDefinition for classes in SQLUser package
7+
result = iris.sql.exec(
8+
"SELECT Name FROM %Dictionary.ClassDefinition WHERE Name %STARTSWITH 'SQLUser.'"
9+
)
10+
for row in result:
11+
print(f"Class: {row[0]}")
12+
13+
print("\nListing tables in SQLUser schema:")
14+
result = iris.sql.exec(
15+
"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'SQLUser'"
16+
)
17+
for row in result:
18+
print(f"Table: {row[0]}")
19+
20+
21+
if __name__ == "__main__":
22+
try:
23+
list_sqluser_tables()
24+
except Exception as e:
25+
print(f"Error: {e}")

src/iris_pgwire/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
caretdev/sqlalchemy-iris.
77
"""
88

9-
__version__ = "1.0.9"
9+
__version__ = "1.1.0"
1010
__author__ = "IRIS PGWire Team"
1111

1212
# Don't import server/protocol in __init__ to avoid sys.modules conflicts

src/iris_pgwire/iris_executor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3045,8 +3045,8 @@ def _sync_execute():
30453045
# Log the actual SQL being sent to IRIS for debugging
30463046
logger.info(
30473047
"About to execute iris.sql.exec",
3048+
sql=optimized_sql[:1000], # Log first 1000 chars
30483049
sql_ends_with_semicolon=optimized_sql.rstrip().endswith(";"),
3049-
sql_last_20=optimized_sql.rstrip()[-20:],
30503050
has_params=optimized_params is not None and len(optimized_params) > 0,
30513051
session_id=session_id,
30523052
)

src/iris_pgwire/schema_mapper.py

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -68,34 +68,19 @@ def translate_input_schema(sql: str) -> str:
6868
flags=re.IGNORECASE,
6969
)
7070

71-
# Pattern 2: Schema-qualified table names (e.g., public.tablename or public."tablename")
72-
# Match public. followed by identifier (word chars) or quoted identifier
73-
result = re.sub(
74-
r'\bpublic\s*\.\s*"(\w+)"',
75-
rf'{IRIS_SCHEMA}."\1"',
76-
result,
77-
flags=re.IGNORECASE,
78-
)
79-
result = re.sub(
80-
r"\bpublic\s*\.\s*(\w+)",
81-
rf"{IRIS_SCHEMA}.\1",
82-
result,
83-
flags=re.IGNORECASE,
84-
)
85-
86-
# Pattern 3: Double-quoted schema (e.g., "public".tablename or "public"."tablename")
87-
result = re.sub(
88-
r'"public"\s*\.\s*"(\w+)"',
89-
rf'{IRIS_SCHEMA}."\1"',
90-
result,
91-
flags=re.IGNORECASE,
92-
)
93-
result = re.sub(
94-
r'"public"\s*\.\s*(\w+)',
95-
rf"{IRIS_SCHEMA}.\1",
96-
result,
97-
flags=re.IGNORECASE,
98-
)
71+
# Combined robust pattern for public.table, "public".table, public."table", "public"."table"
72+
# Matches: (optional quotes)public(optional quotes) . (optional quotes)tablename(optional quotes)
73+
# Group 1: opening quote for table, Group 2: table name, Group 3: closing quote for table
74+
pattern = r'(?i)\b"?public"?\s*\.\s*(")?(\w+)(")?'
75+
76+
def replace_schema(match):
77+
quoted_table = match.group(1) or ""
78+
table_name = match.group(2)
79+
closing_quote = match.group(3) or ""
80+
# Always use SQLUser (exact case) and preserve table quoting/casing
81+
return f"{IRIS_SCHEMA}.{quoted_table}{table_name}{closing_quote}"
82+
83+
result = re.sub(pattern, replace_schema, result)
9984

10085
return result
10186

0 commit comments

Comments
 (0)