11from typing import List , Optional
22
33import agate
4+ from dbt .adapters .base import Column as BaseColumn
5+
6+ # from dbt.events.functions import fire_event
7+ # from dbt.events.types import SchemaCreation
8+ from dbt .adapters .base .impl import ConstraintSupport
9+ from dbt .adapters .base .meta import available
410from dbt .adapters .base .relation import BaseRelation
5- from dbt .adapters .cache import _make_ref_key_msg
11+ from dbt .adapters .cache import _make_ref_key_dict
12+
13+ # from dbt.adapters.cache import _make_ref_key_msg
614from dbt .adapters .sql import SQLAdapter
715from dbt .adapters .sql .impl import CREATE_SCHEMA_MACRO_NAME
16+ from dbt .contracts .graph .nodes import ColumnLevelConstraint , ConstraintType , ModelLevelConstraint
817from dbt .events .functions import fire_event
918from dbt .events .types import SchemaCreation
1019
@@ -18,9 +27,44 @@ class FabricAdapter(SQLAdapter):
1827 Column = FabricColumn
1928 AdapterSpecificConfigs = FabricConfigs
2029
30+ CONSTRAINT_SUPPORT = {
31+ ConstraintType .check : ConstraintSupport .NOT_SUPPORTED ,
32+ ConstraintType .not_null : ConstraintSupport .ENFORCED ,
33+ ConstraintType .unique : ConstraintSupport .ENFORCED ,
34+ ConstraintType .primary_key : ConstraintSupport .ENFORCED ,
35+ ConstraintType .foreign_key : ConstraintSupport .ENFORCED ,
36+ }
37+
38+ @available .parse (lambda * a , ** k : [])
39+ def get_column_schema_from_query (self , sql : str ) -> List [BaseColumn ]:
40+ """Get a list of the Columns with names and data types from the given sql."""
41+ _ , cursor = self .connections .add_select_query (sql )
42+
43+ columns = [
44+ self .Column .create (
45+ column_name , self .connections .data_type_code_to_name (column_type_code )
46+ )
47+ # https://peps.python.org/pep-0249/#description
48+ for column_name , column_type_code , * _ in cursor .description
49+ ]
50+ return columns
51+
52+ @classmethod
53+ def convert_boolean_type (cls , agate_table , col_idx ):
54+ return "bit"
55+
56+ @classmethod
57+ def convert_datetime_type (cls , agate_table , col_idx ):
58+ return "datetime2(6)"
59+
60+ @classmethod
61+ def convert_number_type (cls , agate_table , col_idx ):
62+ decimals = agate_table .aggregate (agate .MaxPrecision (col_idx ))
63+ return "float" if decimals else "int"
64+
2165 def create_schema (self , relation : BaseRelation ) -> None :
2266 relation = relation .without_identifier ()
23- fire_event (SchemaCreation (relation = _make_ref_key_msg (relation )))
67+ fire_event (SchemaCreation (relation = _make_ref_key_dict (relation )))
2468 macro_name = CREATE_SCHEMA_MACRO_NAME
2569 kwargs = {
2670 "relation" : relation ,
@@ -33,10 +77,6 @@ def create_schema(self, relation: BaseRelation) -> None:
3377 self .execute_macro (macro_name , kwargs = kwargs )
3478 self .commit_if_has_connection ()
3579
36- @classmethod
37- def date_function (cls ):
38- return "getdate()"
39-
4080 @classmethod
4181 def convert_text_type (cls , agate_table , col_idx ):
4282 column = agate_table .columns [col_idx ]
@@ -46,23 +86,14 @@ def convert_text_type(cls, agate_table, col_idx):
4686 length = max_len if max_len > 16 else 16
4787 return "varchar({})" .format (length )
4888
49- @classmethod
50- def convert_datetime_type (cls , agate_table , col_idx ):
51- return "datetime2(6)"
52-
53- @classmethod
54- def convert_boolean_type (cls , agate_table , col_idx ):
55- return "bit"
56-
57- @classmethod
58- def convert_number_type (cls , agate_table , col_idx ):
59- decimals = agate_table .aggregate (agate .MaxPrecision (col_idx ))
60- return "float" if decimals else "int"
61-
6289 @classmethod
6390 def convert_time_type (cls , agate_table , col_idx ):
6491 return "time(6)"
6592
93+ @classmethod
94+ def date_function (cls ):
95+ return "getdate()"
96+
6697 # Methods used in adapter tests
6798 def timestamp_add_sql (self , add_to : str , number : int = 1 , interval : str = "hour" ) -> str :
6899 # note: 'interval' is not supported for T-SQL
@@ -145,6 +176,45 @@ def run_sql_for_tests(self, sql, fetch, conn):
145176 finally :
146177 conn .transaction_open = False
147178
179+ @available
180+ @classmethod
181+ def render_column_constraint (cls , constraint : ColumnLevelConstraint ) -> Optional [str ]:
182+ rendered_column_constraint = None
183+ if constraint .type == ConstraintType .not_null :
184+ rendered_column_constraint = "not null "
185+ else :
186+ rendered_column_constraint = ""
187+
188+ if rendered_column_constraint :
189+ rendered_column_constraint = rendered_column_constraint .strip ()
190+
191+ return rendered_column_constraint
192+
193+ @classmethod
194+ def render_model_constraint (cls , constraint : ModelLevelConstraint ) -> Optional [str ]:
195+ constraint_prefix = "add constraint "
196+ column_list = ", " .join (constraint .columns )
197+
198+ if constraint .type == ConstraintType .unique :
199+ return (
200+ constraint_prefix
201+ + f"{ constraint .name } unique nonclustered({ column_list } ) not enforced"
202+ )
203+ elif constraint .type == ConstraintType .primary_key :
204+ return (
205+ constraint_prefix
206+ + f"{ constraint .name } primary key nonclustered({ column_list } ) not enforced"
207+ )
208+ elif constraint .type == ConstraintType .foreign_key and constraint .expression :
209+ return (
210+ constraint_prefix
211+ + f"{ constraint .name } foreign key({ column_list } ) references { constraint .expression } not enforced"
212+ )
213+ elif constraint .type == ConstraintType .custom and constraint .expression :
214+ return f"{ constraint_prefix } { constraint .expression } "
215+ else :
216+ return None
217+
148218
149219COLUMNS_EQUAL_SQL = """
150220with diff_count as (
0 commit comments