Skip to content

Commit adf9f8d

Browse files
authored
Merge pull request #156 from microsoft/v1.8.3
Added Fabric Relation
2 parents 0b3a906 + 8a4dd41 commit adf9f8d

File tree

16 files changed

+288
-86
lines changed

16 files changed

+288
-86
lines changed

dbt/adapters/fabric/__version__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version = "1.8.2"
1+
version = "1.8.3"

dbt/adapters/fabric/fabric_adapter.py

+2-16
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
from dbt.adapters.fabric.fabric_column import FabricColumn
2222
from dbt.adapters.fabric.fabric_configs import FabricConfigs
2323
from dbt.adapters.fabric.fabric_connection_manager import FabricConnectionManager
24+
from dbt.adapters.fabric.fabric_relation import FabricRelation
2425

2526

2627
class FabricAdapter(SQLAdapter):
2728
ConnectionManager = FabricConnectionManager
2829
Column = FabricColumn
2930
AdapterSpecificConfigs = FabricConfigs
31+
Relation = FabricRelation
3032

3133
_capabilities: CapabilityDict = CapabilityDict(
3234
{
@@ -183,22 +185,6 @@ def run_sql_for_tests(self, sql, fetch, conn):
183185
finally:
184186
conn.transaction_open = False
185187

186-
def render_limited(self) -> str:
187-
rendered = self.render()
188-
if self.limit is None:
189-
return rendered
190-
elif self.limit == 0:
191-
return f"(select * from {rendered} where 1=0) _dbt_top_subq"
192-
else:
193-
return f"(select TOP {self.limit} * from {rendered}) _dbt_top_subq"
194-
195-
# TODO: Standardizing quote characters
196-
# def quoted(self, identifier):
197-
# return "[{identifier}]".format(
198-
# quote_char=self.quote_character,
199-
# identifier=identifier,
200-
# )
201-
202188
@available
203189
@classmethod
204190
def render_column_constraint(cls, constraint: ColumnLevelConstraint) -> Optional[str]:
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from dataclasses import dataclass, field
2+
from typing import Optional, Type
3+
4+
from dbt.adapters.base.relation import BaseRelation
5+
from dbt.adapters.utils import classproperty
6+
7+
from dbt.adapters.fabric.relation_configs import FabricQuotePolicy, FabricRelationType
8+
9+
10+
@dataclass(frozen=True, eq=False, repr=False)
11+
class FabricRelation(BaseRelation):
12+
type: Optional[FabricRelationType] = None # type: ignore
13+
quote_policy: FabricQuotePolicy = field(default_factory=lambda: FabricQuotePolicy())
14+
15+
@classproperty
16+
def get_relation_type(cls) -> Type[FabricRelationType]:
17+
return FabricRelationType
18+
19+
@classmethod
20+
def render_limited(self) -> str:
21+
rendered = self.render(self=self)
22+
if self.limit is None:
23+
return rendered
24+
elif self.limit == 0:
25+
return f"(select * from {rendered} where 1=0) _dbt_top_subq"
26+
else:
27+
return f"(select TOP {self.limit} * from {rendered}) _dbt_top_subq"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from dbt.adapters.fabric.relation_configs.policies import (
2+
FabricIncludePolicy,
3+
FabricQuotePolicy,
4+
FabricRelationType,
5+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from dataclasses import dataclass
2+
from typing import Any, Dict
3+
4+
import agate
5+
from dbt.adapters.base.relation import Policy
6+
from dbt.adapters.contracts.relation import RelationConfig
7+
from dbt.adapters.relation_configs import RelationConfigBase, RelationResults
8+
9+
from dbt.adapters.fabric.relation_configs.policies import FabricIncludePolicy, FabricQuotePolicy
10+
11+
12+
@dataclass(frozen=True, eq=True, unsafe_hash=True)
13+
class FabricRelationConfigBase(RelationConfigBase):
14+
"""
15+
This base class implements a few boilerplate methods and provides some light structure for Fabric relations.
16+
"""
17+
18+
@classmethod
19+
def include_policy(cls) -> Policy:
20+
return FabricIncludePolicy()
21+
22+
@classmethod
23+
def quote_policy(cls) -> Policy:
24+
return FabricQuotePolicy()
25+
26+
@classmethod
27+
def from_relation_config(cls, relation_config: RelationConfig):
28+
relation_config_dict = cls.parse_relation_config(relation_config)
29+
relation = cls.from_dict(relation_config_dict)
30+
return relation
31+
32+
@classmethod
33+
def parse_relation_config(cls, relation_config: RelationConfig) -> Dict:
34+
raise NotImplementedError(
35+
"`parse_relation_config()` needs to be implemented on this RelationConfigBase instance"
36+
)
37+
38+
@classmethod
39+
def from_relation_results(cls, relation_results: RelationResults):
40+
relation_config = cls.parse_relation_results(relation_results)
41+
relation = cls.from_dict(relation_config)
42+
return relation # type: ignore
43+
44+
@classmethod
45+
def parse_relation_results(cls, relation_results: RelationResults) -> Dict[str, Any]:
46+
raise NotImplementedError(
47+
"`parse_relation_results()` needs to be implemented on this RelationConfigBase instance"
48+
)
49+
50+
# @classmethod
51+
# def _render_part(cls, component: ComponentName, value: Optional[str]) -> Optional[str]:
52+
# if cls.include_policy().get_part(component) and value:
53+
# if cls.quote_policy().get_part(component):
54+
# return f"[{value}]"
55+
# return value.lower()
56+
# return None
57+
58+
@classmethod
59+
def _get_first_row(cls, results: agate.Table) -> agate.Row:
60+
try:
61+
return results.rows[0]
62+
except IndexError:
63+
return agate.Row(values=set())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from dataclasses import dataclass
2+
3+
from dbt.adapters.base.relation import Policy
4+
from dbt_common.dataclass_schema import StrEnum
5+
6+
7+
class FabricRelationType(StrEnum):
8+
Table = "table"
9+
View = "view"
10+
CTE = "cte"
11+
12+
13+
class FabricIncludePolicy(Policy):
14+
database: bool = True
15+
schema: bool = True
16+
identifier: bool = True
17+
18+
19+
@dataclass
20+
class FabricQuotePolicy(Policy):
21+
database: bool = True
22+
schema: bool = True
23+
identifier: bool = True

dbt/include/fabric/macros/adapters/metadata.sql

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
{% macro fabric__list_schemas(database) %}
1313
{% call statement('list_schemas', fetch_result=True, auto_begin=False) -%}
14-
1514
select name as [schema]
1615
from sys.schemas {{ information_schema_hints() }}
1716
{% endcall %}
@@ -28,6 +27,7 @@
2827

2928
{% macro fabric__list_relations_without_caching(schema_relation) -%}
3029
{% call statement('list_relations_without_caching', fetch_result=True) -%}
30+
USE [{{ schema_relation.database }}];
3131
with base as (
3232
select
3333
DB_NAME() as [database],
@@ -51,6 +51,7 @@
5151

5252
{% macro fabric__get_relation_without_caching(schema_relation) -%}
5353
{% call statement('get_relation_without_caching', fetch_result=True) -%}
54+
USE [{{ schema_relation.database }}];
5455
with base as (
5556
select
5657
DB_NAME() as [database],

dbt/include/fabric/macros/adapters/relation.sql

-30
Original file line numberDiff line numberDiff line change
@@ -39,36 +39,6 @@
3939
path={"schema": reference[0], "identifier": reference[1]})) }}
4040
{% endfor %}
4141
{% elif relation.type == 'table'%}
42-
{# {% call statement('find_references', fetch_result=true) %}
43-
USE [{{ relation.database }}];
44-
SELECT obj.name AS FK_NAME,
45-
sch.name AS [schema_name],
46-
tab1.name AS [table],
47-
col1.name AS [column],
48-
tab2.name AS [referenced_table],
49-
col2.name AS [referenced_column]
50-
FROM sys.foreign_key_columns fkc
51-
INNER JOIN sys.objects obj
52-
ON obj.object_id = fkc.constraint_object_id
53-
INNER JOIN sys.tables tab1
54-
ON tab1.object_id = fkc.parent_object_id
55-
INNER JOIN sys.schemas sch
56-
ON tab1.schema_id = sch.schema_id
57-
INNER JOIN sys.columns col1
58-
ON col1.column_id = parent_column_id AND col1.object_id = tab1.object_id
59-
INNER JOIN sys.tables tab2
60-
ON tab2.object_id = fkc.referenced_object_id
61-
INNER JOIN sys.columns col2
62-
ON col2.column_id = referenced_column_id AND col2.object_id = tab2.object_id
63-
WHERE sch.name = '{{ relation.schema }}' and tab2.name = '{{ relation.identifier }}'
64-
{% endcall %}
65-
{% set references = load_result('find_references')['data'] %}
66-
{% for reference in references -%}
67-
-- dropping referenced table {{ reference[0] }}.{{ reference[1] }}
68-
{{ fabric__drop_relation_script(relation.incorporate(
69-
type="table",
70-
path={"schema": reference[1], "identifier": reference[2]})) }}
71-
{% endfor %} #}
7242
{% set object_id_type = 'U' %}
7343

7444
{%- else -%}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
2+
{% materialization incremental, adapter='fabric' -%}
3+
4+
{%- set full_refresh_mode = (should_full_refresh()) -%}
5+
{% set target_relation = this.incorporate(type='table') %}
6+
{%- set relations_list = fabric__get_relation_without_caching(target_relation) -%}
7+
8+
{%- set existing_relation = none %}
9+
{% if (relations_list|length == 1) and (relations_list[0][2] == target_relation.schema)
10+
and (relations_list[0][1] == target_relation.identifier) and (relations_list[0][3] == target_relation.type)%}
11+
{% set existing_relation = target_relation %}
12+
{% elif (relations_list|length == 1) and (relations_list[0][2] == target_relation.schema)
13+
and (relations_list[0][1] == target_relation.identifier) and (relations_list[0][3] != target_relation.type) %}
14+
{% set existing_relation = get_or_create_relation(relations_list[0][0], relations_list[0][2] , relations_list[0][1] , relations_list[0][3])[1] %}
15+
{% endif %}
16+
17+
{{ log("Full refresh mode" ~ full_refresh_mode)}}
18+
{{ log("existing relation : "~existing_relation ~ " type "~ existing_relation.type ~ " is view? "~existing_relation.is_view) }}
19+
{{ log("target relation: " ~target_relation ~ " type "~ target_relation.type ~ " is view? "~target_relation.is_view) }}
20+
21+
-- configs
22+
{%- set unique_key = config.get('unique_key') -%}
23+
{% set incremental_strategy = config.get('incremental_strategy') or 'default' %}
24+
{%- set temp_relation = make_temp_relation(target_relation)-%}
25+
26+
{% set grant_config = config.get('grants') %}
27+
{%- set on_schema_change = incremental_validate_on_schema_change(config.get('on_schema_change'), default='ignore') -%}
28+
29+
{{ run_hooks(pre_hooks, inside_transaction=True) }}
30+
31+
{% if existing_relation is none %}
32+
33+
{%- call statement('main') -%}
34+
{{ fabric__create_table_as(False, target_relation, sql)}}
35+
{%- endcall -%}
36+
37+
{% elif existing_relation.is_view %}
38+
39+
{#-- Can't overwrite a view with a table - we must drop --#}
40+
{{ log("Dropping relation " ~ target_relation ~ " because it is a view and this model is a table.") }}
41+
{{ drop_relation_if_exists(existing_relation) }}
42+
{%- call statement('main') -%}
43+
{{ fabric__create_table_as(False, target_relation, sql)}}
44+
{%- endcall -%}
45+
46+
{% elif full_refresh_mode %}
47+
48+
{%- call statement('main') -%}
49+
{{ fabric__create_table_as(False, target_relation, sql)}}
50+
{%- endcall -%}
51+
52+
{% else %}
53+
54+
{%- call statement('create_tmp_relation') -%}
55+
{{ fabric__create_table_as(True, temp_relation, sql)}}
56+
{%- endcall -%}
57+
{% do adapter.expand_target_column_types(
58+
from_relation=temp_relation,
59+
to_relation=target_relation) %}
60+
{#-- Process schema changes. Returns dict of changes if successful. Use source columns for upserting/merging --#}
61+
{% set dest_columns = process_schema_changes(on_schema_change, temp_relation, existing_relation) %}
62+
{% if not dest_columns %}
63+
{% set dest_columns = adapter.get_columns_in_relation(existing_relation) %}
64+
{% endif %}
65+
66+
{#-- Get the incremental_strategy, the macro to use for the strategy, and build the sql --#}
67+
{% set incremental_predicates = config.get('predicates', none) or config.get('incremental_predicates', none) %}
68+
{% set strategy_sql_macro_func = adapter.get_incremental_strategy_macro(context, incremental_strategy) %}
69+
{% set strategy_arg_dict = ({'target_relation': target_relation, 'temp_relation': temp_relation, 'unique_key': unique_key, 'dest_columns': dest_columns, 'incremental_predicates': incremental_predicates }) %}
70+
{%- call statement('main') -%}
71+
{{ strategy_sql_macro_func(strategy_arg_dict) }}
72+
{%- endcall -%}
73+
{% endif %}
74+
75+
{% do drop_relation_if_exists(temp_relation) %}
76+
{{ run_hooks(post_hooks, inside_transaction=True) }}
77+
78+
{% set target_relation = target_relation.incorporate(type='table') %}
79+
80+
{% set should_revoke = should_revoke(existing_relation, full_refresh_mode) %}
81+
{% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}
82+
83+
{% do persist_docs(target_relation, model) %}
84+
{% do adapter.commit() %}
85+
{{ return({'relations': [target_relation]}) }}
86+
87+
{%- endmaterialization %}

dbt/include/fabric/macros/materializations/models/incremental/incremental_strategies.sql

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
{% if arg_dict["unique_key"] %}
44
{% do return(get_incremental_delete_insert_sql(arg_dict)) %}
55
{% else %}
6+
-- Incremental Append will insert data into target table.
67
{% do return(get_incremental_append_sql(arg_dict)) %}
78
{% endif %}
89

dbt/include/fabric/macros/materializations/models/table/table.sql

+30-13
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
11
{% materialization table, adapter='fabric' %}
22

3-
-- Create target relation
3+
-- Load target relation
44
{%- set target_relation = this.incorporate(type='table') %}
5+
-- Load existing relation
6+
{%- set relation = fabric__get_relation_without_caching(this) %}
7+
8+
{% set existing_relation = none %}
9+
{% if (relation|length == 1) %}
10+
{% set existing_relation = get_or_create_relation(relation[0][0], relation[0][2] , relation[0][1] , relation[0][3])[1] %}
11+
{% endif %}
12+
13+
{%- set backup_relation = none %}
14+
{{log("Existing Relation type is "~ existing_relation.type)}}
15+
{% if (existing_relation != none and existing_relation.type == "table") %}
16+
{%- set backup_relation = make_backup_relation(target_relation, 'table') -%}
17+
{% elif (existing_relation != none and existing_relation.type == "view") %}
18+
{%- set backup_relation = make_backup_relation(target_relation, 'view') -%}
19+
{% endif %}
20+
21+
{% if (existing_relation != none) %}
22+
-- drop the temp relations if they exist already in the database
23+
{{ drop_relation_if_exists(backup_relation) }}
24+
-- Rename target relation as backup relation
25+
{{ adapter.rename_relation(existing_relation, backup_relation) }}
26+
{% endif %}
27+
528
-- grab current tables grants config for comparision later on
629
{% set grant_config = config.get('grants') %}
7-
{%- set backup_relation = make_backup_relation(target_relation, 'table') -%}
8-
-- drop the temp relations if they exist already in the database
9-
{{ drop_relation_if_exists(backup_relation) }}
10-
-- Rename target relation as backup relation
11-
{%- set relation = fabric__get_relation_without_caching(target_relation) %}
12-
{% if relation|length > 0 %}
13-
{{ adapter.rename_relation(target_relation, backup_relation) }}
14-
{% endif %}
1530

1631
{{ run_hooks(pre_hooks, inside_transaction=False) }}
1732
-- `BEGIN` happens here:
@@ -29,10 +44,12 @@
2944
-- `COMMIT` happens here
3045
{{ adapter.commit() }}
3146

32-
-- finally, drop the foreign key references if exists
33-
{{ drop_fk_indexes_on_table(backup_relation) }}
34-
-- drop existing/backup relation after the commit
35-
{{ drop_relation_if_exists(backup_relation) }}
47+
{% if (backup_relation != none) %}
48+
-- finally, drop the foreign key references if exists
49+
{{ drop_fk_indexes_on_table(backup_relation) }}
50+
-- drop existing/backup relation after the commit
51+
{{ drop_relation_if_exists(backup_relation) }}
52+
{% endif %}
3653
-- Add constraints including FK relation.
3754
{{ fabric__build_model_constraints(target_relation) }}
3855
{{ run_hooks(post_hooks, inside_transaction=False) }}

0 commit comments

Comments
 (0)