diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 402e6715..c45d116c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -48,14 +48,14 @@ repos: - '--line-length' - '99' - '--python-version' - - '39' + - '310' - repo: 'https://github.com/psf/black' rev: 26.5.1 hooks: - id: black args: - '--line-length=99' - - '--target-version=py39' + - '--target-version=py310' - id: black alias: black-check stages: diff --git a/Makefile b/Makefile index defc31e1..cb142592 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,7 @@ server: ## Spins up a local MS SQL Server instance for development. Docker-compo docker compose up -d .PHONY: clean +clean: ## Removes ignored files and build artifacts from the repo. @echo "cleaning repo" @git clean -f -X diff --git a/README.md b/README.md index 3c431ec0..f3a9fe22 100644 --- a/README.md +++ b/README.md @@ -98,51 +98,30 @@ See [the changelog](CHANGELOG.md) ## Configuration -- `dbt_sqlserver_use_default_schema_concat`: *(default: `false`)* Controls schema name generation when a [custom schema](https://docs.getdbt.com/docs/build/custom-schemas) is set on a model. - - | Flag value | `custom_schema_name` | Result | - |---|---|---| - | `false` (default, legacy) | *(none)* | `target.schema` | - | `false` (default, legacy) | `"reporting"` | `reporting` | - | `true` (dbt-core standard) | *(none)* | `target.schema` | - | `true` (dbt-core standard) | `"reporting"` | `target.schema_reporting` | - - When `false` (the default), the adapter uses its legacy behaviour: `custom_schema_name` is used **as-is** without being prefixed by `target.schema`. - When `true`, the adapter delegates to dbt-core's `default__generate_schema_name`, which concatenates `target.schema` + `_` + `custom_schema_name`. - - **Example usage in `dbt_project.yml`:** - - ```yaml - flags: - dbt_sqlserver_use_default_schema_concat: true # Enable standard schema concatenation - ``` - - This adapter also supports the same setting via `vars:` for backwards compatibility, so either method works in the current release. - - > **Note:** If you want to permanently customise schema generation and avoid any future changes, override the `sqlserver__generate_schema_name` macro directly in your project instead. - - ### `dbt_sqlserver_use_default_schema_concat` *(default: `false`)* Controls schema name generation when a [custom schema](https://docs.getdbt.com/docs/build/custom-schemas) is set on a model. -| Value | `custom_schema_name` | Result | +| Flag value | `custom_schema_name` | Result | |---|---|---| -| `false` (default) | *(none)* | `target.schema` | -| `false` (default) | `"reporting"` | `reporting` | -| `true` | *(none)* | `target.schema` | -| `true` | `"reporting"` | `target.schema_reporting` | +| `false` (default, legacy) | *(none)* | `target.schema` | +| `false` (default, legacy) | `"reporting"` | `reporting` | +| `true` (dbt-core standard) | *(none)* | `target.schema` | +| `true` (dbt-core standard) | `"reporting"` | `target.schema_reporting` | -When `false`, `custom_schema_name` is used as-is without being prefixed by `target.schema`. -When `true`, the adapter delegates to dbt-core's `default__generate_schema_name`. +When `false` (the default), the adapter uses its legacy behaviour: `custom_schema_name` is used **as-is** without being prefixed by `target.schema`. +When `true`, the adapter delegates to dbt-core's `default__generate_schema_name`, which concatenates `target.schema` + `_` + `custom_schema_name`. + +**Example usage in `dbt_project.yml`:** ```yaml -# dbt_project.yml -vars: - dbt_sqlserver_use_default_schema_concat: true +flags: + dbt_sqlserver_use_default_schema_concat: true # Enable standard schema concatenation ``` -> **Note:** To permanently customise schema generation without a flag dependency, override the `sqlserver__generate_schema_name` macro directly in your project. +The same setting is also honoured via `vars:` for backwards compatibility; the behavior flag under `flags:` takes precedence when both are set. + +> **Note:** If you want to permanently customise schema generation and avoid any future changes, override the `sqlserver__generate_schema_name` macro directly in your project instead. ### `backend` diff --git a/dbt/adapters/sqlserver/sqlserver_constants.py b/dbt/adapters/sqlserver/sqlserver_constants.py index ecae4e8c..9ee5fdcb 100644 --- a/dbt/adapters/sqlserver/sqlserver_constants.py +++ b/dbt/adapters/sqlserver/sqlserver_constants.py @@ -102,7 +102,7 @@ "str": "varchar", "uuid.UUID": "uniqueidentifier", "uuid": "uniqueidentifier", - "float": "bigint", + "float": "float", "int": "int", "bytes": "varbinary", "bytearray": "varbinary", diff --git a/dbt/include/sqlserver/macros/adapters/indexes.sql b/dbt/include/sqlserver/macros/adapters/indexes.sql index 2fd3733a..278d93ac 100644 --- a/dbt/include/sqlserver/macros/adapters/indexes.sql +++ b/dbt/include/sqlserver/macros/adapters/indexes.sql @@ -1,6 +1,6 @@ {% macro sqlserver__create_clustered_columnstore_index(relation) -%} {%- set cci_name = (relation.schema ~ '_' ~ relation.identifier ~ '_cci') | replace(".", "") | replace(" ", "") -%} - {%- set relation_name = relation.schema ~ '_' ~ relation.identifier -%} + {%- set relation_name = relation.include(database=False) -%} {%- set full_relation = '"' ~ relation.schema ~ '"."' ~ relation.identifier ~ '"' -%} use [{{ relation.database }}]; if EXISTS ( diff --git a/dbt/include/sqlserver/macros/adapters/metadata.sql b/dbt/include/sqlserver/macros/adapters/metadata.sql index 062a0c4f..a7237708 100644 --- a/dbt/include/sqlserver/macros/adapters/metadata.sql +++ b/dbt/include/sqlserver/macros/adapters/metadata.sql @@ -1,6 +1,7 @@ {% macro get_query_options(parse_options=False) %} {{ log (config.get('query_tag','dbt-sqlserver'))}} - {%- set query_label = config.get('query_tag','dbt-sqlserver') -%} + {#- Escape single quotes so a query_tag like "it's" can't break out of the LABEL literal. -#} + {%- set query_label = escape_single_quotes(config.get('query_tag','dbt-sqlserver')) -%} {%- set query_options = config.get('query_options', {}) -%} {%- set query_options_raw = config.get('query_options_raw', []) -%} @@ -77,7 +78,7 @@ incremental, snapshot, unit_test), override get_query_options instead. -#} {% macro apply_label() %} {{ log (config.get('query_tag','dbt-sqlserver'))}} - {%- set query_label = config.get('query_tag','dbt-sqlserver') -%} + {%- set query_label = escape_single_quotes(config.get('query_tag','dbt-sqlserver')) -%} OPTION (LABEL = '{{query_label}}'); {% endmacro %} diff --git a/dbt/include/sqlserver/macros/materializations/models/incremental/incremental_strategies.sql b/dbt/include/sqlserver/macros/materializations/models/incremental/incremental_strategies.sql index 393d7020..2adb1f34 100644 --- a/dbt/include/sqlserver/macros/materializations/models/incremental/incremental_strategies.sql +++ b/dbt/include/sqlserver/macros/materializations/models/incremental/incremental_strategies.sql @@ -1,7 +1,7 @@ {% macro sqlserver__get_incremental_default_sql(arg_dict) %} {% if arg_dict["unique_key"] %} - -- Delete + Insert Strategy, calls get_delete_insert_merge_sql + -- Merge strategy: emits a MERGE statement via get_incremental_merge_sql {% do return(get_incremental_merge_sql(arg_dict)) %} {% else %} -- Incremental Append will insert data into target table. diff --git a/dbt/include/sqlserver/macros/materializations/models/table/table_dml_refresh.sql b/dbt/include/sqlserver/macros/materializations/models/table/table_dml_refresh.sql index 4a5d095c..33fffc04 100644 --- a/dbt/include/sqlserver/macros/materializations/models/table/table_dml_refresh.sql +++ b/dbt/include/sqlserver/macros/materializations/models/table/table_dml_refresh.sql @@ -56,7 +56,13 @@ {# Atomic DML swap — RCSI protects concurrent readers #} {# dbt-sqlserver uses autocommit=True and add_begin_query/add_commit_query #} {# are no-ops, so this creates a simple (non-nested) transaction. #} + {# SET XACT_ABORT ON makes the whole transaction roll back if any statement #} + {# fails. Without it, statement-aborting errors on the INSERT (e.g. NULL or #} + {# constraint violations) do not stop the batch: the DELETE stands and the #} + {# trailing COMMIT still executes, leaving the target committed-empty — #} + {# silent data loss. (Verified against SQL Server 2022.) #} {% call statement('dml_refresh_swap') -%} + SET XACT_ABORT ON; BEGIN TRANSACTION; DELETE FROM {{ target_relation }}; INSERT INTO {{ target_relation }} ({{ column_list }}) diff --git a/dbt/include/sqlserver/profile_template.yml b/dbt/include/sqlserver/profile_template.yml index 32c80711..abe07dc4 100644 --- a/dbt/include/sqlserver/profile_template.yml +++ b/dbt/include/sqlserver/profile_template.yml @@ -4,7 +4,7 @@ prompts: host: hint: "your host name" port: - default: 5432 + default: 1433 type: "int" user: hint: "dev username"