From df0e7d600e775fa21e4d1ee1fd1187aa193eaddd Mon Sep 17 00:00:00 2001 From: mbiyashev3 Date: Tue, 18 Aug 2026 12:45:06 -0400 Subject: [PATCH 1/2] fix: guard None target.threads in all adapter invocations overrides dbt Fusion (dbt_version 2.0.0) leaves target.threads as None. #549 added a guard, but only to default__get_invocations_dml_sql. Because adapter.dispatch prefers an adapter-specific override, every warehouse with its own macro still rendered the bare Python literal `None` into the invocations INSERT, which the warehouse rejects: BigQuery: Unrecognized name: None at [42:9] invocations is 8th in the upload order, so the failure also aborts the sources/tests/models uploads and orphans the already-inserted model_executions / test_executions rows on command_invocation_id. Guards bigquery__, postgres__, trino__ and sqlserver__. Null literal chosen per branch by surrounding construct: - bigquery/postgres/trino emit a bare tuple consumed as `insert into () values ()`, so the target column type is known and an untyped `null` is unambiguous. - sqlserver wraps its tuple in `select ... from (values (...)) v (...)`, where the derived column's type comes from the row rather than the insert target, so a typed null is required. Uses dbt.type_int() to match how target_threads is declared in models/sources/invocations.sql. Values-only change: no columns added, removed or reordered. Co-Authored-By: Claude Opus 5 (1M context) --- .../upload_invocations.sql | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/macros/upload_individual_datasets/upload_invocations.sql b/macros/upload_individual_datasets/upload_invocations.sql index 2fc18334..1f000cff 100644 --- a/macros/upload_individual_datasets/upload_invocations.sql +++ b/macros/upload_individual_datasets/upload_invocations.sql @@ -109,7 +109,8 @@ '{{ target.profile_name }}', {# target_profile_name #} '{{ target.name }}', {# target_name #} '{{ target.schema }}', {# target_schema #} - {{ target.threads }}, {# target_threads #} + {# dbt-fusion sets target.threads to None — guard against it #} + {% if target.threads is not none %}{{ target.threads }}{% else %}null{% endif %}, {# target_threads #} '{{ env_var('DBT_CLOUD_PROJECT_ID', '') }}', {# dbt_cloud_project_id #} '{{ env_var('DBT_CLOUD_JOB_ID', '') }}', {# dbt_cloud_job_id #} @@ -172,7 +173,8 @@ '{{ target.profile_name }}', {# target_profile_name #} '{{ target.name }}', {# target_name #} '{{ target.schema }}', {# target_schema #} - {{ target.threads }}, {# target_threads #} + {# dbt-fusion sets target.threads to None — guard against it #} + {% if target.threads is not none %}{{ target.threads }}{% else %}null{% endif %}, {# target_threads #} '{{ env_var("DBT_CLOUD_PROJECT_ID", "") }}', {# dbt_cloud_project_id #} '{{ env_var("DBT_CLOUD_JOB_ID", "") }}', {# dbt_cloud_job_id #} @@ -234,7 +236,8 @@ '{{ target.profile_name }}', {# target_profile_name #} '{{ target.name }}', {# target_name #} '{{ target.schema }}', {# target_schema #} - {{ target.threads }}, {# target_threads #} + {# dbt-fusion sets target.threads to None — guard against it #} + {% if target.threads is not none %}{{ target.threads }}{% else %}null{% endif %}, {# target_threads #} '{{ env_var("DBT_CLOUD_PROJECT_ID", "") }}', {# dbt_cloud_project_id #} '{{ env_var("DBT_CLOUD_JOB_ID", "") }}', {# dbt_cloud_job_id #} @@ -317,7 +320,8 @@ '{{ target.profile_name }}', {# target_profile_name #} '{{ target.name }}', {# target_name #} '{{ target.schema }}', {# target_schema #} - {{ target.threads }}, {# target_threads #} + {# dbt-fusion sets target.threads to None — guard against it #} + {% if target.threads is not none %}{{ target.threads }}{% else %}cast(null as {{ dbt.type_int() }}){% endif %}, {# target_threads #} '{{ env_var('DBT_CLOUD_PROJECT_ID', '') }}', {# dbt_cloud_project_id #} '{{ env_var('DBT_CLOUD_JOB_ID', '') }}', {# dbt_cloud_job_id #} From 337099ce5c0bab4eccc24a5f3307fd86192c5a5a Mon Sep 17 00:00:00 2001 From: mbiyashev3 Date: Tue, 18 Aug 2026 12:45:41 -0400 Subject: [PATCH 2/2] fix: guard empty-string failures in all adapter test_executions overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #549 replaced the `failures` expression in default__get_test_executions_dml_sql with a guard that handles both None and the empty string, but left the five adapter overrides on the older form: {{ 'null' if test.failures is none else test.failures }} That form handles None but not ''. When test.failures is an empty string the expression renders to nothing at all, so the VALUES tuple collapses to `..., null, , 'message', ...` — an empty positional value, which is a syntax error on every warehouse. Aligns bigquery__, postgres__, snowflake__, sqlserver__ and trino__ with the default__ form from #549. Note snowflake__ was previously fixed for invocations only by falling through to default__; test_executions has its own override and so was still on the old form. A literal 0 continues to render as 0 -- the guard tests `is not none` and `!= ''` rather than truthiness, so a legitimate zero-failure count is not collapsed to null. Values-only change: no columns added, removed or reordered. Co-Authored-By: Claude Opus 5 (1M context) --- .../upload_test_executions.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/macros/upload_individual_datasets/upload_test_executions.sql b/macros/upload_individual_datasets/upload_test_executions.sql index 54c0d92d..2cec6fed 100644 --- a/macros/upload_individual_datasets/upload_test_executions.sql +++ b/macros/upload_individual_datasets/upload_test_executions.sql @@ -79,7 +79,7 @@ {{ test.execution_time }}, {# total_node_runtime #} null, {# rows_affected not available in Databricks #} - {{ 'null' if test.failures is none else test.failures }}, {# failures #} + {{ test.failures if test.failures is not none and test.failures != '' else 'null' }}, {# failures #} '{{ test.message | replace("\\", "\\\\") | replace("'", "\\'") | replace('"', '\\"') | replace("\n", "\\n") }}', {# message #} {{ adapter.dispatch('parse_json', 'dbt_artifacts')(tojson(test.adapter_response) | replace("\\", "\\\\") | replace("'", "\\'") | replace('"', '\\"')) }} {# adapter_response #} ) @@ -133,7 +133,7 @@ {{ test.execution_time }}, {# total_node_runtime #} null, {# rows_affected not available in Databricks #} - {{ 'null' if test.failures is none else test.failures }}, {# failures #} + {{ test.failures if test.failures is not none and test.failures != '' else 'null' }}, {# failures #} $${{ test.message }}$$, {# message #} $${{ tojson(test.adapter_response) }}$$ {# adapter_response #} ) @@ -186,7 +186,7 @@ {{ test.execution_time }}, {# total_node_runtime #} try_cast('{{ test.adapter_response.rows_affected }}' as int), {# rows_affected #} - {{ 'null' if test.failures is none else test.failures }}, {# failures #} + {{ test.failures if test.failures is not none and test.failures != '' else 'null' }}, {# failures #} '{{ test.message | replace("\\", "\\\\") | replace("'", "\\'") | replace('"', '\\"') }}', {# message #} '{{ tojson(test.adapter_response) | replace("\\", "\\\\") | replace("'", "\\'") | replace('"', '\\"') }}' {# adapter_response #} ) @@ -226,7 +226,7 @@ {{ test.execution_time }}, {# total_node_runtime #} null, {# rows_affected not available in Databricks #} - {{ 'null' if test.failures is none else test.failures }}, {# failures #} + {{ test.failures if test.failures is not none and test.failures != '' else 'null' }}, {# failures #} '{{ test.message | replace("'", "''") }}', {# message #} '{{ tojson(test.adapter_response) | replace("'", "''") }}' {# adapter_response #} ) @@ -276,7 +276,7 @@ {% endif %} , {# rows_affected #} - {{ 'null' if test.failures is none else test.failures }}, {# failures #} + {{ test.failures if test.failures is not none and test.failures != '' else 'null' }}, {# failures #} '{{ test.message | replace("'", "''") }}', {# message #} '{{ tojson(test.adapter_response) | replace("'", "''") }}' {# adapter_response #} )