Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
6 changes: 1 addition & 5 deletions dbt/adapters/sqlserver/sqlserver_connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from dbt.adapters.fabric.fabric_connection_manager import (
AZURE_CREDENTIAL_SCOPE,
bool_to_connection_string_arg,
get_pyodbc_attrs_before_accesstoken,
get_pyodbc_attrs_before_credentials,
)

Expand Down Expand Up @@ -136,10 +135,7 @@ def open(cls, connection: Connection) -> Connection:
def connect():
logger.debug(f"Using connection string: {con_str_display}")

if credentials.authentication == "ActiveDirectoryAccessToken":
attrs_before = get_pyodbc_attrs_before_accesstoken(credentials.access_token)
else:
attrs_before = get_pyodbc_attrs_before_credentials(credentials)
attrs_before = get_pyodbc_attrs_before_credentials(credentials)

handle = pyodbc.connect(
con_str_concat,
Expand Down
4 changes: 2 additions & 2 deletions dbt/adapters/sqlserver/sqlserver_relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ def render_limited(self) -> str:
if self.limit is None:
return rendered
elif self.limit == 0:
return f"(select * from {rendered} where 1=0) {self._render_limited_alias()}"
return f"(select * from {rendered} where 1=0) AS {self._render_limited_alias()}"
else:
return f"(select TOP {self.limit} * from {rendered}) {self._render_limited_alias()}"
return f"(select TOP {self.limit} * from {rendered}) AS {self._render_limited_alias()}"

def __post_init__(self):
# Check for length of Redshift table/view names.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{% macro check_for_nested_cte(sql) %}
{% if execute %} {# Ensure this runs only at execution time #}
{% set cleaned_sql = sql | lower | replace("\n", " ") %} {# Convert to lowercase and remove newlines #}
{% set cte_count = cleaned_sql.count("with ") %} {# Count occurrences of "WITH " #}
{% if cte_count > 1 %}
{{ return(True) }}
{% else %}
{{ return(False) }} {# No nested CTEs found #}
{% endif %}
{% else %}
{{ return(False) }} {# Return False during parsing #}
{% endif %}
{% endmacro %}

{% macro sqlserver__unit_test_create_table_as(temporary, relation, sql) -%}
{%- set query_label = apply_label() -%}
{%- set contract_config = config.get('contract') -%}
{%- set is_nested_cte = check_for_nested_cte(sql) -%}

{%- if is_nested_cte -%}
{{ exceptions.warn(
"Nested CTE warning: Nested CTEs do not support CTAS. However, 2-level nested CTEs are supported due to a code bug. Please expect this fix in the future."
) }}
{%- endif -%}

{%- if is_nested_cte and contract_config.enforced -%}

{{ exceptions.raise_compiler_error(
"Unit test Materialization error: Since the contract is enforced and the model contains a nested CTE, unit tests cannot be materialized. Please refactor your model or unenforce model and try again."
) }}

{%- elif not is_nested_cte and contract_config.enforced -%}

{# Build CREATE TABLE + INSERT using a temporary view to avoid CTAS semantics #}
CREATE TABLE {{ relation }}
{{ build_columns_constraints(relation) }}
{{ get_assert_columns_equivalent(sql) }};

{%- set listColumns -%}
{%- for column in model['columns'] -%}
{{ "["~column~"]" }}{{ ", " if not loop.last }}
{%- endfor -%}
{%- endset -%}

{%- set tmp_vw_relation = relation.incorporate(path={"identifier": relation.identifier ~ '__dbt_tmp_vw'}, type='view') -%}
{%- do adapter.drop_relation(tmp_vw_relation) -%}
{{ get_create_view_as_sql(tmp_vw_relation, sql) }}

INSERT INTO {{ relation }} ({{ listColumns }})
SELECT {{ listColumns }} FROM {{ tmp_vw_relation }} {{ query_label }};

DROP VIEW IF EXISTS {{ tmp_vw_relation.schema }}.{{ tmp_vw_relation.identifier }};

{%- else -%}

{# Default: use SELECT INTO from an intermediate view so CTEs are preserved and labels are placed inside the selectable statement #}
{%- set tmp_vw_relation = relation.incorporate(path={"identifier": relation.identifier ~ '__dbt_tmp_vw'}, type='view') -%}
{%- do adapter.drop_relation(tmp_vw_relation) -%}

{{ get_create_view_as_sql(tmp_vw_relation, sql) }}

SELECT * INTO {{ relation }} FROM {{ tmp_vw_relation }} {{ query_label }};

DROP VIEW IF EXISTS {{ tmp_vw_relation.schema }}.{{ tmp_vw_relation.identifier }};

{%- endif -%}

{%- endmacro %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{%- materialization unit, adapter='sqlserver' -%}

{% set relations = [] %}

{% set expected_rows = config.get('expected_rows') %}
{% set expected_sql = config.get('expected_sql') %}
{% set tested_expected_column_names = expected_rows[0].keys() if (expected_rows | length ) > 0 else get_columns_in_query(sql) %}

{%- set target_relation = this.incorporate(type='table') -%}
{%- set temp_relation = make_temp_relation(target_relation)-%}
{% do run_query(sqlserver__unit_test_create_table_as(True, temp_relation, get_empty_subquery_sql(sql))) %}
{%- set columns_in_relation = adapter.get_columns_in_relation(temp_relation) -%}
{%- set column_name_to_data_types = {} -%}
{%- set column_name_to_quoted = {} -%}
{%- for column in columns_in_relation -%}
{%- do column_name_to_data_types.update({column.name|lower: column.data_type}) -%}
{%- do column_name_to_quoted.update({column.name|lower: column.quoted}) -%}
{%- endfor -%}

{%- set expected_column_names_quoted = [] -%}
{%- for column_name in tested_expected_column_names -%}
{%- do expected_column_names_quoted.append(column_name_to_quoted[column_name]) -%}
{%- endfor -%}

{% if not expected_sql %}
{% set expected_sql = get_expected_sql(expected_rows, column_name_to_data_types, column_name_to_quoted) %}
{% endif %}
{% set unit_test_sql = get_unit_test_sql(sql, expected_sql, expected_column_names_quoted) %}

{% call statement('main', fetch_result=True) -%}

{{ unit_test_sql }}

{%- endcall %}

{% do adapter.drop_relation(temp_relation) %}

{{ return({'relations': relations}) }}

{%- endmaterialization -%}
17 changes: 17 additions & 0 deletions dbt/include/sqlserver/macros/unit_test_sql/get_fixture_sql.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{% macro get_expected_sql(rows, column_name_to_data_types, column_name_to_quoted) %}

{%- if (rows | length) == 0 -%}
select top 0 * from dbt_internal_unit_test_actual where 1=0
{%- else -%}
{%- for row in rows -%}
{%- set formatted_row = format_row(row, column_name_to_data_types) -%}
select
{%- for column_name, column_value in formatted_row.items() %} {{ column_value }} as {{ column_name_to_quoted[column_name] }}{% if not loop.last -%}, {%- endif %}
{%- endfor %}
{%- if not loop.last %}
union all
{% endif %}
{%- endfor -%}
{%- endif -%}

{% endmacro %}
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def run(self):
packages=find_namespace_packages(include=["dbt", "dbt.*"]),
include_package_data=True,
install_requires=[
"dbt-fabric==1.9.3",
"dbt-fabric==1.9.6",
"dbt-core>=1.9.0,<2.0",
"dbt-common>=1.0,<2.0",
"dbt-adapters>=1.11.0,<2.0",
Expand Down