Repository: dbt-labs/dbt-core

Issue:
Add support for BigQuery execution project in BigQuery adapter
### Describe the feature
This feature adds support for BigQuery execution project in BigQuery adapter through the adapter profile configuration so that the database client uses slots from the reservation assigned to that project as explained in [BigQuery documentation](https://cloud.google.com/bigquery/docs/reservations-intro).

My current workaround is to use the project I want to use the reservation on through the adapter profile configuration and specify the project in the `dbt_project.iml` for models to ensure the outputs are in the appropriate project.

### Describe alternatives you've considered
Another approach would be to use the `project` for the database client and to provide a `database_project` to use it as the default project for storing the outputs (opposed to the `project` that would be used for execution).

### Additional context
This feature is only relevant to BigQuery as far as I know.

### Who will this benefit?
It will be useful for any company using multiple BigQuery reservations/projects (such as the one at work at).
Regarding current workaround I use, it's clearly inconvenient as we remove the database override when we're working with a development environment and we have to remember not to commit it as we're versioning our `dbt_project.iml`.

### Are you interested in contributing this feature?
Yes, I'm just not sure it's really testable beside checking it's properly reading the appropriate field in the configuration.



Discussion:

Resolving patch:
diff --git a/plugins/bigquery/dbt/adapters/bigquery/connections.py b/plugins/bigquery/dbt/adapters/bigquery/connections.py
--- a/plugins/bigquery/dbt/adapters/bigquery/connections.py
+++ b/plugins/bigquery/dbt/adapters/bigquery/connections.py
@@ -83,6 +83,7 @@ class BigQueryCredentials(Credentials):
     # BigQuery allows an empty database / project, where it defers to the
     # environment for the project
     database: Optional[str]
+    execution_project: Optional[str] = None
     timeout_seconds: Optional[int] = 300
     location: Optional[str] = None
     priority: Optional[Priority] = None
@@ -130,6 +131,9 @@ def __pre_deserialize__(cls, d: Dict[Any, Any]) -> Dict[Any, Any]:
         if 'database' not in d:
             _, database = get_bigquery_defaults()
             d['database'] = database
+        # `execution_project` default to dataset/project
+        if 'execution_project' not in d:
+            d['execution_project'] = d['database']
         return d
 
 
@@ -252,12 +256,12 @@ def get_bigquery_client(cls, profile_credentials):
                 cls.get_impersonated_bigquery_credentials(profile_credentials)
         else:
             creds = cls.get_bigquery_credentials(profile_credentials)
-        database = profile_credentials.database
+        execution_project = profile_credentials.execution_project
         location = getattr(profile_credentials, 'location', None)
 
         info = client_info.ClientInfo(user_agent=f'dbt-{dbt_version}')
         return google.cloud.bigquery.Client(
-            database,
+            execution_project,
             creds,
             location=location,
             client_info=info,


Test patch:
diff --git a/test/integration/022_bigquery_test/execution-project-models/model.sql b/test/integration/022_bigquery_test/execution-project-models/model.sql
new file mode 100644
--- /dev/null
+++ b/test/integration/022_bigquery_test/execution-project-models/model.sql
@@ -0,0 +1 @@
+select 1 as id
diff --git a/test/integration/022_bigquery_test/execution-project-models/schema.yml b/test/integration/022_bigquery_test/execution-project-models/schema.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/022_bigquery_test/execution-project-models/schema.yml
@@ -0,0 +1,10 @@
+version: 2
+models:
+- name: model
+  description: |
+    I'm testing the profile execution_project
+  tests:
+    - project_for_job_id:
+        region: region-us
+        project_id: "{{ project_id}}"
+        unique_schema_id: "{{ unique_schema_id }}"
\ No newline at end of file
diff --git a/test/integration/022_bigquery_test/macros/test_project_for_job_id.sql b/test/integration/022_bigquery_test/macros/test_project_for_job_id.sql
new file mode 100644
--- /dev/null
+++ b/test/integration/022_bigquery_test/macros/test_project_for_job_id.sql
@@ -0,0 +1,7 @@
+{% test project_for_job_id(model, region, unique_schema_id, project_id) %}
+select 1
+from `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
+where date(creation_time) = current_date
+  and job_project = {{project_id}}
+  and destination_table.dataset_id = {{unique_schema_id}}
+{% endtest %}
diff --git a/test/integration/022_bigquery_test/test_bigquery_execution_project.py b/test/integration/022_bigquery_test/test_bigquery_execution_project.py
new file mode 100644
--- /dev/null
+++ b/test/integration/022_bigquery_test/test_bigquery_execution_project.py
@@ -0,0 +1,23 @@
+import os
+from test.integration.base import DBTIntegrationTest, use_profile
+
+
+class TestAlternateExecutionProjectBigQueryRun(DBTIntegrationTest):
+    @property
+    def schema(self):
+        return "bigquery_test_022"
+
+    @property
+    def models(self):
+        return "execution-project-models"
+
+    @use_profile('bigquery')
+    def test__bigquery_execute_project(self):
+        results = self.run_dbt(['run', '--models', 'model'])
+        self.assertEqual(len(results), 1)
+        execution_project = os.environ['BIGQUERY_TEST_ALT_DATABASE']
+        self.run_dbt(['test',
+                      '--target', 'alternate',
+                      '--vars', '{ project_id: %s, unique_schema_id: %s }'
+                      % (execution_project, self.unique_schema())],
+                     expect_pass=False)
diff --git a/test/integration/base.py b/test/integration/base.py
--- a/test/integration/base.py
+++ b/test/integration/base.py
@@ -263,6 +263,15 @@ def bigquery_profile(self):
                         'keyfile_json': credentials,
                         'schema': self.unique_schema(),
                     },
+                    'alternate': {
+                        'type': 'bigquery',
+                        'method': 'service-account-json',
+                        'threads': 1,
+                        'project': project_id,
+                        'keyfile_json': credentials,
+                        'schema': self.unique_schema(),
+                        'execution_project': self.alternative_database,
+                    },
                 },
                 'target': 'default2'
             }