fix(dwh): align adapter sampling contract
This commit is contained in:
@@ -26,7 +26,7 @@
|
||||
- Test: `harness/tests/test_dwh_port_contract.py`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `DwhCapabilities`, `DwhAdapter`, `DwhHealth`, and `UnsupportedCapability`.
|
||||
- Produces: `DwhCapabilities`, `DwhAdapter`, `DwhHealth`, `DistinctValues`, and `UnsupportedCapability`.
|
||||
- Consumes: existing catalog models from `tht.db.introspect` and execution result types from `tht.db.execute`.
|
||||
|
||||
- [ ] **Step 1: Write the failing protocol-shape test**
|
||||
@@ -54,16 +54,21 @@ class DwhCapabilities:
|
||||
sampling: bool = True
|
||||
distinct_values: bool = True
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DistinctValues:
|
||||
values: list[object]
|
||||
truncated: bool
|
||||
|
||||
@runtime_checkable
|
||||
class DwhAdapter(Protocol):
|
||||
@property
|
||||
def capabilities(self) -> DwhCapabilities: ...
|
||||
def health(self) -> DwhHealth: ...
|
||||
def introspect(self) -> DatabaseCatalog: ...
|
||||
def run_query(self, sql: str, *, limit: int | None = None) -> QueryResult: ...
|
||||
def introspect(self) -> PhysicalSchema: ...
|
||||
def run_query(self, sql: str, *, limit: int) -> ExecResult: ...
|
||||
def explain(self, sql: str) -> PlanSummary: ...
|
||||
def sample_column(self, table: str, column: str, *, limit: int) -> list[object]: ...
|
||||
def distinct_values(self, table: str, column: str) -> list[object]: ...
|
||||
def distinct_values(self, table: str, column: str) -> DistinctValues: ...
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run contract test and type-oriented import smoke test**
|
||||
@@ -97,8 +102,8 @@ git commit -m "refactor(dwh): define adapter contract"
|
||||
```python
|
||||
@pytest.mark.parametrize("factory", [postgres_factory, rest_factory])
|
||||
def test_adapter_rejects_write_sql(factory):
|
||||
with pytest.raises(ReadOnlyViolation):
|
||||
factory().run_query("delete from fact_sales")
|
||||
with pytest.raises(ExecutionError):
|
||||
factory().run_query("delete from fact_sales", limit=10)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify failure**
|
||||
@@ -111,12 +116,17 @@ Expected: FAIL because the adapter classes are absent.
|
||||
```python
|
||||
class PostgresDwhAdapter:
|
||||
capabilities = DwhCapabilities()
|
||||
def __init__(self, config: DatabaseConfig): self._config = config
|
||||
def run_query(self, sql: str, *, limit: int | None = None) -> QueryResult:
|
||||
return run_query(self._config, sql, limit=limit)
|
||||
def __init__(self, config: DatabaseConfig):
|
||||
self._config = config
|
||||
self._engine = make_engine(config)
|
||||
def run_query(self, sql: str, *, limit: int) -> ExecResult:
|
||||
return run_query(self._engine, sql, limit=limit)
|
||||
```
|
||||
|
||||
Implement the analogous REST wrapper by delegating to `tht.rest.*`; translate transport-specific errors only at the adapter boundary.
|
||||
Implement the analogous REST wrapper by delegating to `tht.rest.*`; translate transport-specific
|
||||
errors only at the adapter boundary. Both wrappers delegate frequency-ranked, distinct sampling to
|
||||
the paired implementations in `tht.db.sampling`. A non-positive query limit is rejected, and
|
||||
`distinct_values` reports any cap through `DistinctValues.truncated`.
|
||||
|
||||
- [ ] **Step 4: Run adapter, read-only, sampling, and REST tests**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user