Skip to content

Commit 404ed7c

Browse files
committed
Comprehensive refactoring and test environment stabilization
- Removed hardcoded 'SQLUser' schema name from all source and test files. - Modernized catalog emulation and executors to use configurable IRIS_SCHEMA. - Fixed test environment to automatically include 'src' in pythonpath. - Updated all regression and integration tests to support dynamic schema mapping. - Verified all 21 tests pass without manual environment overrides.
1 parent 35a3be4 commit 404ed7c

38 files changed

+708
-859
lines changed

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ ignore = [
202202
"B023", # function definition does not bind loop variable - common in regex replacement functions
203203
]
204204

205+
[tool.ruff.lint.isort]
206+
known-first-party = ["iris_pgwire"]
207+
src = ["src"]
208+
205209
[tool.ruff.lint.per-file-ignores]
206210
"__init__.py" = ["F401"]
207211
"src/iris_pgwire/conversions/**/*.py" = [
@@ -229,6 +233,7 @@ ignore = [
229233

230234
[tool.mypy]
231235
python_version = "3.11"
236+
mypy_path = "src"
232237
check_untyped_defs = true
233238
disallow_any_generics = true
234239
disallow_incomplete_defs = true
@@ -258,6 +263,7 @@ generate-badge = "."
258263
badge-format = "svg"
259264

260265
[tool.pytest.ini_options]
266+
pythonpath = ["src"]
261267
minversion = "7.0"
262268
testpaths = ["tests"]
263269
python_files = ["test_*.py"]

pytest.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
# Pytest configuration for iris-pgwire project
33
# P6 COPY Protocol testing configuration
44

5+
# Test paths
6+
pythonpath = src
7+
58
# Test discovery patterns
69
python_files = test_*.py
710
python_classes = Test*

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.2.11"
9+
__version__ = "1.2.12"
1010
__author__ = "Thomas Dyar <thomas.dyar@intersystems.com>"
1111

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

src/iris_pgwire/catalog/catalog_functions.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import structlog
2727

2828
from ..type_mapping import TypeModifier, get_type_by_oid
29+
from iris_pgwire.schema_mapper import IRIS_SCHEMA
2930
from .oid_generator import OIDGenerator
3031

3132
logger = structlog.get_logger()
@@ -134,9 +135,7 @@ def format_type(self, type_oid: int, typmod: int) -> str | None:
134135
# T008: pg_get_constraintdef(constraint_oid, pretty?)
135136
# ========================================================================
136137

137-
def pg_get_constraintdef(
138-
self, constraint_oid: int, pretty: bool = False
139-
) -> str | None:
138+
def pg_get_constraintdef(self, constraint_oid: int, pretty: bool = False) -> str | None:
140139
"""
141140
Get constraint definition as SQL text.
142141
@@ -238,7 +237,7 @@ def pg_get_serial_sequence(self, table: str, column: str) -> str | None:
238237
if "." in table:
239238
schema, table_name = table.split(".", 1)
240239
else:
241-
schema = "SQLUser" # Default IRIS schema
240+
schema = IRIS_SCHEMA # Default IRIS schema
242241
table_name = table
243242

244243
# Query IRIS INFORMATION_SCHEMA.COLUMNS for auto-increment
@@ -260,24 +259,26 @@ def pg_get_serial_sequence(self, table: str, column: str) -> str | None:
260259
is_identity = row[1] if len(row) > 1 else None
261260

262261
# Check for auto-increment indicators
263-
if is_identity == "YES" or (column_default and "IDENTITY" in str(column_default).upper()):
262+
if is_identity == "YES" or (
263+
column_default and "IDENTITY" in str(column_default).upper()
264+
):
264265
# PostgreSQL convention: table_column_seq
265266
sequence_name = f"{table_name}_{column}_seq"
266267
return f"public.{sequence_name}"
267268

268269
return None
269270

270271
except Exception as e:
271-
logger.warning("Error checking serial sequence", table=table, column=column, error=str(e))
272+
logger.warning(
273+
"Error checking serial sequence", table=table, column=column, error=str(e)
274+
)
272275
return None
273276

274277
# ========================================================================
275278
# T010: pg_get_indexdef(index_oid, column?, pretty?)
276279
# ========================================================================
277280

278-
def pg_get_indexdef(
279-
self, index_oid: int, column: int = 0, pretty: bool = False
280-
) -> str | None:
281+
def pg_get_indexdef(self, index_oid: int, column: int = 0, pretty: bool = False) -> str | None:
281282
"""
282283
Get CREATE INDEX statement for an index.
283284
@@ -357,10 +358,10 @@ def _get_constraint_metadata(self, constraint_oid: int) -> dict[str, Any] | None
357358
"""
358359
# Reverse lookup constraint name from OID (requires OID cache)
359360
# For now, query all constraints and match OID
360-
query = """
361+
query = f"""
361362
SELECT CONSTRAINT_SCHEMA, CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME
362363
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
363-
WHERE CONSTRAINT_SCHEMA = 'SQLUser'
364+
WHERE CONSTRAINT_SCHEMA = '{IRIS_SCHEMA}'
364365
"""
365366

366367
try:
@@ -371,7 +372,7 @@ def _get_constraint_metadata(self, constraint_oid: int) -> dict[str, Any] | None
371372
# Find matching OID
372373
for row in result["rows"]:
373374
schema, name, ctype, table = row[:4]
374-
computed_oid = self.oid_gen.get_constraint_oid(schema, name)
375+
computed_oid = self.oid_gen.get_constraint_oid(name, schema)
375376
if computed_oid == constraint_oid:
376377
return {
377378
"constraint_schema": schema,
@@ -383,7 +384,9 @@ def _get_constraint_metadata(self, constraint_oid: int) -> dict[str, Any] | None
383384
return None
384385

385386
except Exception as e:
386-
logger.error("Error querying constraint metadata", constraint_oid=constraint_oid, error=str(e))
387+
logger.error(
388+
"Error querying constraint metadata", constraint_oid=constraint_oid, error=str(e)
389+
)
387390
return None
388391

389392
def _get_constraint_columns(self, schema: str, constraint_name: str) -> list[str]:
@@ -413,7 +416,9 @@ def _get_constraint_columns(self, schema: str, constraint_name: str) -> list[str
413416
return [row[0].lower() for row in result["rows"]]
414417

415418
except Exception as e:
416-
logger.error("Error querying constraint columns", constraint_name=constraint_name, error=str(e))
419+
logger.error(
420+
"Error querying constraint columns", constraint_name=constraint_name, error=str(e)
421+
)
417422
return []
418423

419424
def _get_fk_references(self, schema: str, constraint_name: str) -> dict[str, Any] | None:
@@ -468,7 +473,9 @@ def _get_fk_references(self, schema: str, constraint_name: str) -> dict[str, Any
468473
}
469474

470475
except Exception as e:
471-
logger.error("Error querying FK references", constraint_name=constraint_name, error=str(e))
476+
logger.error(
477+
"Error querying FK references", constraint_name=constraint_name, error=str(e)
478+
)
472479
return None
473480

474481
def _get_index_metadata(self, index_oid: int) -> dict[str, Any] | None:

src/iris_pgwire/catalog/catalog_router.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from dataclasses import dataclass, field
1010
from typing import Any
1111

12+
from iris_pgwire.schema_mapper import IRIS_SCHEMA
1213
from .oid_generator import OIDGenerator
1314

1415

@@ -226,7 +227,7 @@ def has_regclass_cast(self, query: str) -> bool:
226227
"""
227228
return bool(self._regclass_pattern.search(query))
228229

229-
def resolve_regclass(self, table_name: str, schema: str = "SQLUser") -> int:
230+
def resolve_regclass(self, table_name: str, schema: str = IRIS_SCHEMA) -> int:
230231
"""
231232
Resolve table name to OID (like ::regclass).
232233
@@ -246,9 +247,9 @@ def resolve_regclass(self, table_name: str, schema: str = "SQLUser") -> int:
246247
# Handle quoted identifiers
247248
table_name = table_name.strip('"')
248249

249-
return self.oid_gen.get_table_oid(schema, table_name)
250+
return self.oid_gen.get_table_oid(table_name, schema)
250251

251-
def translate_regclass_casts(self, query: str, schema: str = "SQLUser") -> str:
252+
def translate_regclass_casts(self, query: str, schema: str = IRIS_SCHEMA) -> str:
252253
"""
253254
Replace 'tablename'::regclass with resolved OID.
254255

src/iris_pgwire/catalog/oid_generator.py

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,21 @@
1414
- information_schema: 11323
1515
"""
1616

17+
from iris_pgwire.schema_mapper import IRIS_SCHEMA
1718
import hashlib
1819
from dataclasses import dataclass
1920
from typing import Literal
2021

21-
ObjectType = Literal[
22-
"namespace", "table", "column", "constraint", "index", "type", "default"
23-
]
22+
ObjectType = Literal["namespace", "table", "column", "constraint", "index", "type", "default"]
2423

2524

2625
@dataclass
2726
class ObjectIdentity:
2827
"""Identity tuple for OID generation."""
2928

30-
namespace: str # Schema name (e.g., 'SQLUser')
31-
object_type: ObjectType # Object category
3229
object_name: str # Fully qualified name (e.g., 'users' or 'users.id')
30+
namespace: str = IRIS_SCHEMA # Schema name (e.g., '{IRIS_SCHEMA}')
31+
object_type: ObjectType = "table" # Object category
3332

3433
@property
3534
def identity_string(self) -> str:
@@ -46,8 +45,8 @@ class OIDGenerator:
4645
4746
Usage:
4847
gen = OIDGenerator()
49-
table_oid = gen.get_oid('SQLUser', 'table', 'users')
50-
column_oid = gen.get_oid('SQLUser', 'column', 'users.id')
48+
table_oid = gen.get_oid('{IRIS_SCHEMA}', 'table', 'users')
49+
column_oid = gen.get_oid('{IRIS_SCHEMA}', 'column', 'users.id')
5150
"""
5251

5352
# Well-known namespace OIDs (match PostgreSQL)
@@ -65,14 +64,14 @@ def __init__(self):
6564
"""Initialize OID generator with empty cache."""
6665
self._cache: dict[str, int] = {}
6766

68-
def get_oid(self, namespace: str, object_type: str, object_name: str) -> int:
67+
def get_oid(self, object_type: str, object_name: str, namespace: str = IRIS_SCHEMA) -> int:
6968
"""
7069
Generate deterministic OID for an object.
7170
7271
Args:
73-
namespace: Schema name (e.g., 'SQLUser')
7472
object_type: Object category ('table', 'column', 'constraint', etc.)
7573
object_name: Object name (e.g., 'users' or 'users.id' for columns)
74+
namespace: Schema name (e.g., '{IRIS_SCHEMA}')
7675
7776
Returns:
7877
Deterministic OID in user range (>= 16384)
@@ -95,7 +94,7 @@ def get_oid_from_identity(self, identity: ObjectIdentity) -> int:
9594
Returns:
9695
Deterministic OID
9796
"""
98-
return self.get_oid(identity.namespace, identity.object_type, identity.object_name)
97+
return self.get_oid(identity.object_type, identity.object_name, identity.namespace)
9998

10099
def _generate_oid(self, identity_string: str) -> int:
101100
"""
@@ -141,57 +140,57 @@ def get_namespace_oid(self, namespace: str) -> int:
141140
return self.WELL_KNOWN_NAMESPACES[ns_lower]
142141

143142
# Generate OID for custom namespace
144-
return self.get_oid("", "namespace", namespace)
143+
return self.get_oid("namespace", namespace, "")
145144

146-
def get_table_oid(self, schema: str, table_name: str) -> int:
145+
def get_table_oid(self, table_name: str, schema: str = IRIS_SCHEMA) -> int:
147146
"""
148147
Convenience method to get OID for a table.
149148
150149
Args:
151-
schema: Schema name (e.g., 'SQLUser')
152150
table_name: Table name (e.g., 'users')
151+
schema: Schema name (e.g., '{IRIS_SCHEMA}')
153152
154153
Returns:
155154
Table OID
156155
"""
157-
return self.get_oid(schema, "table", table_name)
156+
return self.get_oid("table", table_name, schema)
158157

159-
def get_column_oid(self, schema: str, table_name: str, column_name: str) -> int:
158+
def get_column_oid(self, table_name: str, column_name: str, schema: str = IRIS_SCHEMA) -> int:
160159
"""
161160
Convenience method to get OID for a column.
162161
163162
Args:
164-
schema: Schema name
165163
table_name: Table name
166164
column_name: Column name
165+
schema: Schema name
167166
168167
Returns:
169168
Column OID
170169
"""
171-
return self.get_oid(schema, "column", f"{table_name}.{column_name}")
170+
return self.get_oid("column", f"{table_name}.{column_name}", schema)
172171

173-
def get_constraint_oid(self, schema: str, constraint_name: str) -> int:
172+
def get_constraint_oid(self, constraint_name: str, schema: str = IRIS_SCHEMA) -> int:
174173
"""
175174
Convenience method to get OID for a constraint.
176175
177176
Args:
178-
schema: Schema name
179177
constraint_name: Constraint name
178+
schema: Schema name
180179
181180
Returns:
182181
Constraint OID
183182
"""
184-
return self.get_oid(schema, "constraint", constraint_name)
183+
return self.get_oid("constraint", constraint_name, schema)
185184

186-
def get_index_oid(self, schema: str, index_name: str) -> int:
185+
def get_index_oid(self, index_name: str, schema: str = IRIS_SCHEMA) -> int:
187186
"""
188187
Convenience method to get OID for an index.
189188
190189
Args:
191-
schema: Schema name
192190
index_name: Index name
191+
schema: Schema name
193192
194193
Returns:
195194
Index OID
196195
"""
197-
return self.get_oid(schema, "index", index_name)
196+
return self.get_oid("index", index_name, schema)

src/iris_pgwire/catalog/pg_attrdef.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from dataclasses import dataclass
1212
from typing import Any
1313

14+
from iris_pgwire.schema_mapper import IRIS_SCHEMA
1415
from .oid_generator import OIDGenerator
1516

1617

@@ -49,34 +50,32 @@ def __init__(self, oid_generator: OIDGenerator):
4950

5051
def from_iris_default(
5152
self,
52-
schema: str,
5353
table_name: str,
5454
column_name: str,
5555
column_position: int,
5656
default_value: str,
57+
schema: str = IRIS_SCHEMA,
5758
) -> PgAttrdef:
5859
"""
5960
Convert IRIS column default to pg_attrdef row.
6061
6162
Args:
62-
schema: IRIS schema name (e.g., 'SQLUser')
6363
table_name: Table name
6464
column_name: Column name
6565
column_position: Column position (attnum, 1-based)
6666
default_value: Default value expression from IRIS
67+
schema: IRIS schema name (e.g., '{IRIS_SCHEMA}')
6768
6869
Returns:
6970
PgAttrdef instance
7071
"""
71-
table_oid = self.oid_gen.get_table_oid(schema, table_name)
72+
table_oid = self.oid_gen.get_table_oid(table_name, schema)
7273
# Generate unique OID for this default
7374
default_key = f"{schema}:{table_name}:{column_name}:default"
74-
default_oid = self.oid_gen.get_oid(schema, "default", default_key)
75+
default_oid = self.oid_gen.get_oid("default", default_key, schema)
7576

7677
# Translate IRIS default to PostgreSQL expression
77-
adbin = self._translate_default(
78-
default_value, schema, table_name, column_name
79-
)
78+
adbin = self._translate_default(default_value, schema, table_name, column_name)
8079

8180
return PgAttrdef(
8281
oid=default_oid,
@@ -117,8 +116,7 @@ def _translate_default(
117116

118117
# Handle timestamp defaults
119118
if any(
120-
ts in upper_default
121-
for ts in ["CURRENT_TIMESTAMP", "NOW()", "GETDATE()", "SYSDATE"]
119+
ts in upper_default for ts in ["CURRENT_TIMESTAMP", "NOW()", "GETDATE()", "SYSDATE"]
122120
):
123121
return "CURRENT_TIMESTAMP"
124122

0 commit comments

Comments
 (0)