From 8bad11cd86a733138dca34661c5a608625834470 Mon Sep 17 00:00:00 2001 From: Pradeep Srikakolapu Date: Mon, 15 Apr 2024 22:38:26 -0700 Subject: [PATCH] Added Fabric Relation --- dbt/adapters/fabric/fabric_adapter.py | 9 --- dbt/adapters/fabric/fabric_relation.py | 27 ++++++++ .../fabric/relation_configs/__init__.py | 5 ++ dbt/adapters/fabric/relation_configs/base.py | 63 +++++++++++++++++++ .../fabric/relation_configs/policies.py | 23 +++++++ tests/functional/adapter/test_empty.py | 4 -- 6 files changed, 118 insertions(+), 13 deletions(-) create mode 100644 dbt/adapters/fabric/fabric_relation.py create mode 100644 dbt/adapters/fabric/relation_configs/__init__.py create mode 100644 dbt/adapters/fabric/relation_configs/base.py create mode 100644 dbt/adapters/fabric/relation_configs/policies.py diff --git a/dbt/adapters/fabric/fabric_adapter.py b/dbt/adapters/fabric/fabric_adapter.py index 1e787e8..ce5885f 100644 --- a/dbt/adapters/fabric/fabric_adapter.py +++ b/dbt/adapters/fabric/fabric_adapter.py @@ -183,15 +183,6 @@ def run_sql_for_tests(self, sql, fetch, conn): finally: conn.transaction_open = False - def render_limited(self) -> str: - rendered = self.render() - if self.limit is None: - return rendered - elif self.limit == 0: - return f"(select * from {rendered} where 1=0) _dbt_top_subq" - else: - return f"(select TOP {self.limit} * from {rendered}) _dbt_top_subq" - # TODO: Standardizing quote characters # def quoted(self, identifier): # return "[{identifier}]".format( diff --git a/dbt/adapters/fabric/fabric_relation.py b/dbt/adapters/fabric/fabric_relation.py new file mode 100644 index 0000000..fbf34ee --- /dev/null +++ b/dbt/adapters/fabric/fabric_relation.py @@ -0,0 +1,27 @@ +from dataclasses import dataclass, field +from typing import Optional, Type + +from dbt.adapters.base.relation import BaseRelation +from dbt.adapters.utils import classproperty + +from dbt.adapters.fabric.relation_configs import FabricQuotePolicy, FabricRelationType + + +@dataclass(frozen=True, eq=False, repr=False) +class FabricRelation(BaseRelation): + type: Optional[FabricRelationType] = None # type: ignore + quote_policy: FabricQuotePolicy = field(default_factory=lambda: FabricQuotePolicy()) + + @classproperty + def get_relation_type(cls) -> Type[FabricRelationType]: + return FabricRelationType + + @classmethod + def render_limited(self) -> str: + rendered = self.render() + if self.limit is None: + return rendered + elif self.limit == 0: + return f"(select * from {rendered} where 1=0) _dbt_top_subq" + else: + return f"(select TOP {self.limit} * from {rendered}) _dbt_top_subq" diff --git a/dbt/adapters/fabric/relation_configs/__init__.py b/dbt/adapters/fabric/relation_configs/__init__.py new file mode 100644 index 0000000..971a0b2 --- /dev/null +++ b/dbt/adapters/fabric/relation_configs/__init__.py @@ -0,0 +1,5 @@ +from dbt.adapters.fabric.relation_configs.policies import ( + FabricIncludePolicy, + FabricQuotePolicy, + FabricRelationType, +) diff --git a/dbt/adapters/fabric/relation_configs/base.py b/dbt/adapters/fabric/relation_configs/base.py new file mode 100644 index 0000000..1ed9a99 --- /dev/null +++ b/dbt/adapters/fabric/relation_configs/base.py @@ -0,0 +1,63 @@ +from dataclasses import dataclass +from typing import Any, Dict, Optional + +import agate +from dbt.adapters.base.relation import Policy +from dbt.adapters.contracts.relation import ComponentName, RelationConfig +from dbt.adapters.relation_configs import RelationConfigBase, RelationResults + +from dbt.adapters.fabric.relation_configs.policies import FabricIncludePolicy, FabricQuotePolicy + + +@dataclass(frozen=True, eq=True, unsafe_hash=True) +class FabricRelationConfigBase(RelationConfigBase): + """ + This base class implements a few boilerplate methods and provides some light structure for Fabric relations. + """ + + @classmethod + def include_policy(cls) -> Policy: + return FabricIncludePolicy() + + @classmethod + def quote_policy(cls) -> Policy: + return FabricQuotePolicy() + + @classmethod + def from_relation_config(cls, relation_config: RelationConfig): + relation_config_dict = cls.parse_relation_config(relation_config) + relation = cls.from_dict(relation_config_dict) + return relation + + @classmethod + def parse_relation_config(cls, relation_config: RelationConfig) -> Dict: + raise NotImplementedError( + "`parse_relation_config()` needs to be implemented on this RelationConfigBase instance" + ) + + @classmethod + def from_relation_results(cls, relation_results: RelationResults): + relation_config = cls.parse_relation_results(relation_results) + relation = cls.from_dict(relation_config) + return relation # type: ignore + + @classmethod + def parse_relation_results(cls, relation_results: RelationResults) -> Dict[str, Any]: + raise NotImplementedError( + "`parse_relation_results()` needs to be implemented on this RelationConfigBase instance" + ) + + @classmethod + def _render_part(cls, component: ComponentName, value: Optional[str]) -> Optional[str]: + if cls.include_policy().get_part(component) and value: + if cls.quote_policy().get_part(component): + return f"[{value}]" + return value.lower() + return None + + @classmethod + def _get_first_row(cls, results: agate.Table) -> agate.Row: + try: + return results.rows[0] + except IndexError: + return agate.Row(values=set()) diff --git a/dbt/adapters/fabric/relation_configs/policies.py b/dbt/adapters/fabric/relation_configs/policies.py new file mode 100644 index 0000000..ceb214d --- /dev/null +++ b/dbt/adapters/fabric/relation_configs/policies.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass + +from dbt.adapters.base.relation import Policy +from dbt_common.dataclass_schema import StrEnum + + +class FabricRelationType(StrEnum): + Table = "table" + View = "view" + CTE = "cte" + + +class FabricIncludePolicy(Policy): + database: bool = True + schema: bool = True + identifier: bool = True + + +@dataclass +class FabricQuotePolicy(Policy): + database: bool = False + schema: bool = False + identifier: bool = False diff --git a/tests/functional/adapter/test_empty.py b/tests/functional/adapter/test_empty.py index dcdd664..040e381 100644 --- a/tests/functional/adapter/test_empty.py +++ b/tests/functional/adapter/test_empty.py @@ -1,9 +1,5 @@ -import pytest from dbt.tests.adapter.empty.test_empty import BaseTestEmpty -@pytest.mark.skip( - reason="render_limited() defaults to dbt-core implementation instead of using Fabric implementation" -) class TestEmpty(BaseTestEmpty): pass