1- from typing import Optional
1+ from typing import List , Optional
22
3+ import agate
34import dbt .exceptions
5+ from dbt .adapters .base import available
46from dbt .adapters .base .impl import ConstraintSupport
5- from dbt .adapters .fabric import FabricAdapter
7+ from dbt .adapters .base .relation import BaseRelation
8+ from dbt .adapters .sql import SQLAdapter
69from dbt .contracts .graph .nodes import ConstraintType
10+ from dbt_common .contracts .constraints import ColumnLevelConstraint
711
812from dbt .adapters .sqlserver .sqlserver_column import SQLServerColumn
913from dbt .adapters .sqlserver .sqlserver_connections import SQLServerConnectionManager
1014from dbt .adapters .sqlserver .sqlserver_relation import SQLServerRelation
1115
1216
13- class SQLServerAdapter (FabricAdapter ):
17+ COLUMNS_EQUAL_SQL = """
18+ with diff_count as (
19+ SELECT
20+ 1 as id,
21+ COUNT(*) as num_missing FROM (
22+ (SELECT {columns} FROM {relation_a} {except_op}
23+ SELECT {columns} FROM {relation_b})
24+ UNION ALL
25+ (SELECT {columns} FROM {relation_b} {except_op}
26+ SELECT {columns} FROM {relation_a})
27+ ) as a
28+ ), table_a as (
29+ SELECT COUNT(*) as num_rows FROM {relation_a}
30+ ), table_b as (
31+ SELECT COUNT(*) as num_rows FROM {relation_b}
32+ ), row_count_diff as (
33+ select
34+ 1 as id,
35+ table_a.num_rows - table_b.num_rows as difference
36+ from table_a, table_b
37+ )
38+ select
39+ row_count_diff.difference as row_count_difference,
40+ diff_count.num_missing as num_mismatched
41+ from row_count_diff
42+ join diff_count on row_count_diff.id = diff_count.id
43+ """ .strip ()
44+
45+
46+ class SQLServerAdapter (SQLAdapter ):
1447 """
15- Controls actual implmentation of adapter, and ability to override certain methods.
48+ Controls actual implementation of adapter, and ability to override certain methods.
1649 """
1750
1851 ConnectionManager = SQLServerConnectionManager
@@ -27,6 +60,91 @@ class SQLServerAdapter(FabricAdapter):
2760 ConstraintType .foreign_key : ConstraintSupport .ENFORCED ,
2861 }
2962
63+ # -- Type conversions (inlined from FabricAdapter) --
64+
65+ @classmethod
66+ def convert_boolean_type (cls , agate_table , col_idx ):
67+ return "bit"
68+
69+ @classmethod
70+ def convert_datetime_type (cls , agate_table , col_idx ):
71+ return "datetime2(6)"
72+
73+ @classmethod
74+ def convert_number_type (cls , agate_table , col_idx ):
75+ decimals = agate_table .aggregate (agate .MaxPrecision (col_idx ))
76+ return "float" if decimals else "int"
77+
78+ @classmethod
79+ def convert_text_type (cls , agate_table , col_idx ):
80+ column = agate_table .columns [col_idx ]
81+ lens = [len (d .encode ("utf-8" )) for d in column .values_without_nulls ()]
82+ max_len = max (lens ) if lens else 64
83+ length = max_len if max_len > 16 else 16
84+ return "varchar({})" .format (length )
85+
86+ @classmethod
87+ def convert_time_type (cls , agate_table , col_idx ):
88+ return "time(6)"
89+
90+ @classmethod
91+ def date_function (cls ):
92+ return "getdate()"
93+
94+ # -- SQL helpers (inlined from FabricAdapter) --
95+
96+ def timestamp_add_sql (self , add_to : str , number : int = 1 , interval : str = "hour" ) -> str :
97+ return f"DATEADD({ interval } ,{ number } ,{ add_to } )"
98+
99+ def string_add_sql (self , add_to : str , value : str , location = "append" ) -> str :
100+ """+ is T-SQL's string concatenation operator"""
101+ if location == "append" :
102+ return f"{ add_to } + '{ value } '"
103+ elif location == "prepend" :
104+ return f"'{ value } ' + { add_to } "
105+ else :
106+ raise ValueError (f'Got an unexpected location value of "{ location } "' )
107+
108+ def get_rows_different_sql (
109+ self ,
110+ relation_a : BaseRelation ,
111+ relation_b : BaseRelation ,
112+ column_names : Optional [List [str ]] = None ,
113+ except_operator : str = "EXCEPT" ,
114+ ) -> str :
115+ names : List [str ]
116+ if column_names is None :
117+ columns = self .get_columns_in_relation (relation_a )
118+ names = sorted ((self .quote (c .name ) for c in columns ))
119+ else :
120+ names = sorted ((self .quote (n ) for n in column_names ))
121+
122+ columns_csv = ", " .join (names )
123+
124+ sql = COLUMNS_EQUAL_SQL .format (
125+ columns = columns_csv ,
126+ relation_a = str (relation_a ),
127+ relation_b = str (relation_b ),
128+ except_op = except_operator ,
129+ )
130+ return sql
131+
132+ # -- Constraint rendering --
133+
134+ @available
135+ @classmethod
136+ def render_column_constraint (cls , constraint : ColumnLevelConstraint ) -> Optional [str ]:
137+ rendered_column_constraint = None
138+ if constraint .type == ConstraintType .not_null :
139+ rendered_column_constraint = "not null"
140+ else :
141+ rendered_column_constraint = ""
142+
143+ if rendered_column_constraint :
144+ rendered_column_constraint = rendered_column_constraint .strip ()
145+
146+ return rendered_column_constraint
147+
30148 @classmethod
31149 def render_model_constraint (cls , constraint ) -> Optional [str ]:
32150 constraint_prefix = "add constraint "
@@ -42,7 +160,10 @@ def render_model_constraint(cls, constraint) -> Optional[str]:
42160 if constraint .type == ConstraintType .unique :
43161 return constraint_prefix + f"{ constraint .name } unique nonclustered({ column_list } )"
44162 elif constraint .type == ConstraintType .primary_key :
45- return constraint_prefix + f"{ constraint .name } primary key nonclustered({ column_list } )"
163+ return (
164+ constraint_prefix
165+ + f"{ constraint .name } primary key nonclustered({ column_list } )"
166+ )
46167 elif constraint .type == ConstraintType .foreign_key and constraint .expression :
47168 return (
48169 constraint_prefix
@@ -56,10 +177,6 @@ def render_model_constraint(cls, constraint) -> Optional[str]:
56177 else :
57178 return None
58179
59- @classmethod
60- def date_function (cls ):
61- return "getdate()"
62-
63180 def valid_incremental_strategies (self ):
64181 """The set of standard builtin strategies which this adapter supports out-of-the-box.
65182 Not used to validate custom strategies defined by end users.
0 commit comments