Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 67 additions & 5 deletions integration_tests/tests/adapter_query_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,58 @@ def _create_adapter(project_dir: str, target: str) -> BaseAdapter:
# Ref resolution
# ------------------------------------------------------------------

def _build_relation_name(self, node: Dict[str, Any]) -> Optional[str]:
"""Construct a relation name from node metadata.

Uses the adapter's ``Relation`` class so that quoting and include
policies are applied correctly for the target warehouse.
Falls back to simple dot-joining if the adapter rejects the inputs.
"""
schema = node.get("schema")
identifier = node.get("alias") or node.get("name")
if not identifier:
return None

database = node.get("database") or ""
try:
relation = self._adapter.Relation.create(
database=database,
schema=schema,
identifier=identifier,
)
return relation.render()
except Exception:
# Some adapters (e.g. ClickHouse) reject certain database values.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we be more deterministic and choose the approach based on the adapter, instead of double try..except?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — replaced the double try/except with the adapter's include_policy. Now we check self._adapter.Relation.get_default_include_policy().database to decide whether to pass the database field. ClickHouse has database=False in its include policy, so we pass "" — no exception handling needed.

See commit d6f6b44.

# Retry without database, then fall back to simple dot-join.
logger.debug(
"Relation.create failed with database=%r for %s; retrying without database",
database,
identifier,
)
try:
relation = self._adapter.Relation.create(
database="",
schema=schema,
identifier=identifier,
)
return relation.render()
except Exception:
logger.debug(
"Relation.create failed without database for %s; using dot-join fallback",
identifier,
)
parts = [p for p in (schema, identifier) if p]
return ".".join(parts) if parts else None

def _load_manifest_maps(self) -> None:
"""Load ref and source maps from the dbt manifest."""
"""Load ref and source maps from the dbt manifest.

Scans both ``nodes`` (enabled models) and ``disabled`` (models
disabled via config, e.g. ``elementary_enabled = False``).
For disabled nodes whose ``relation_name`` is ``None`` (dbt skips
compilation for disabled nodes), the relation name is synthesised
from ``database`` / ``schema`` / ``alias`` via the adapter.
"""
manifest_path = Path(self._project_dir) / "target" / "manifest.json"
if not manifest_path.exists():
raise FileNotFoundError(
Expand All @@ -126,12 +176,24 @@ def _load_manifest_maps(self) -> None:
with open(manifest_path) as fh:
manifest = json.load(fh)

# Collect every node dict: enabled nodes first, then disabled.
all_nodes: List[Dict[str, Any]] = list(manifest.get("nodes", {}).values())
disabled = manifest.get("disabled", {})
for versions in disabled.values():
all_nodes.extend(versions)

ref_map: Dict[str, str] = {}
for node in manifest.get("nodes", {}).values():
relation_name = node.get("relation_name")
for node in all_nodes:
name = node.get("name")
if relation_name and name:
ref_map[name] = relation_name
if not name:
continue
relation_name = node.get("relation_name")
if not relation_name:
relation_name = self._build_relation_name(node)
if relation_name:
# First writer wins — enabled nodes are processed first,
# so a disabled duplicate won't overwrite an enabled one.
ref_map.setdefault(name, relation_name)

source_map: Dict[tuple, str] = {}
for source in manifest.get("sources", {}).values():
Expand Down