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
2 changes: 1 addition & 1 deletion .github/workflows/integration-tests-sqlserver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
name: Regular
strategy:
matrix:
python_version: ["3.9", "3.10", "3.11", "3.12"]
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
msodbc_version: ["17", "18"]
sqlserver_version: ["2017", "2019", "2022"]
collation: ["SQL_Latin1_General_CP1_CS_AS", "SQL_Latin1_General_CP1_CI_AS"]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
publish-docker-client:
strategy:
matrix:
python_version: ["3.9", "3.10", "3.11", "3.12"]
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
docker_target: ["msodbc17", "msodbc18"]
runs-on: ubuntu-latest
permissions:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
name: Unit tests
strategy:
matrix:
python_version: ["3.9", "3.10", "3.11", "3.12"]
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
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,41 @@
{%- 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 can be added once supported by get_expected_sql #}
{% 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 -%}
3 changes: 2 additions & 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 All @@ -86,6 +86,7 @@ def run(self):
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
],
project_urls={
"Setup & configuration": "https://docs.getdbt.com/reference/warehouse-profiles/mssql-profile", # noqa: E501
Expand Down