Repository: GoogleCloudPlatform/magic-modules
Pull request #14026: feat: added `google_bigquery_table` data source
Author: login: ramonvermeulen
Labels: name: service/bigquery
Created: 2025-05-19T15:57:29Z | Merged: 2025-05-22T19:48:47Z

<!--
Complete the self-review checklist to help speed up the review process: https://googlecloudplatform.github.io/magic-modules/contribute/review-pr/

If your PR is still work in progress, please create it in draft mode.

Put a description of what this PR is for here, along with any references to issues that this resolves or contributes to.
For example: Fixes https://github.com/hashicorp/terraform-provider-google/issues/ISSUE_ID
-->

Closes https://github.com/hashicorp/terraform-provider-google/issues/22834

- First time doing a data source implementation

**Release Note Template for Downstream PRs (will be copied)**

See [Write release notes](https://googlecloudplatform.github.io/magic-modules/contribute/release-notes/) for guidance.


**Acceptance tests locally:**
```
TF_ACC=1 TF_SCHEMA_PANIC_ON_ERROR=1 go test ./google/services/bigquery -v -run=TestAccDataSourceGoogleBigqueryTable_basic -timeout 240m -ldflags="-X=github.com/hashicorp/terraform-provider-google/version.ProviderVersion=acc"
=== RUN   TestAccDataSourceGoogleBigqueryTable_basic
=== PAUSE TestAccDataSourceGoogleBigqueryTable_basic
=== CONT  TestAccDataSourceGoogleBigqueryTable_basic
--- PASS: TestAccDataSourceGoogleBigqueryTable_basic (12.87s)
PASS
ok      github.com/hashicorp/terraform-provider-google/google/services/bigquery 13.550s
```

 ~Would like to get some thoughts on the `schema` property, since it has a reference to itself. E.g. when the type = `RECORD` it has a nested schema, following the google docs this has a max depth of 15.~

 ~https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#tableschema~
 ~https://cloud.google.com/bigquery/docs/nested-repeated#limitations~
 ~How are generally slightly more complex data structures like this implemented in data sources?~

Edit: Took a look at https://github.com/GoogleCloudPlatform/magic-modules/pull/13908 and implemented in a similar manner, handling `schema` just as the same type as in the original resource (in this case json as string).

Via 

```go
dsSchema := tpgresource.DatasourceSchemaFromResourceSchema(ResourceBigQueryTable().Schema)
```

```release-note:new-datasource
`google_bigquery_table`
```


Diff:
diff --git a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl
index 5e601cd64c54..2db33bf6c018 100644
--- a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl
+++ b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl
@@ -44,6 +44,7 @@ var handwrittenDatasources = map[string]*schema.Resource{
 	"google_beyondcorp_app_gateway":                    beyondcorp.DataSourceGoogleBeyondcorpAppGateway(),
 	"google_beyondcorp_security_gateway":               beyondcorp.DataSourceGoogleBeyondcorpSecurityGateway(),
 	"google_billing_account":                           billing.DataSourceGoogleBillingAccount(),
+	"google_bigquery_table":          								  bigquery.DataSourceGoogleBigQueryTable(),
 	"google_bigquery_tables":          								  bigquery.DataSourceGoogleBigQueryTables(),
 	"google_bigquery_dataset":          								bigquery.DataSourceGoogleBigqueryDataset(),
 	"google_bigquery_default_service_account":          bigquery.DataSourceGoogleBigqueryDefaultServiceAccount(),
diff --git a/mmv1/third_party/terraform/services/bigquery/data_source_google_bigquery_table.go b/mmv1/third_party/terraform/services/bigquery/data_source_google_bigquery_table.go
new file mode 100644
index 000000000000..c08d51788af8
--- /dev/null
+++ b/mmv1/third_party/terraform/services/bigquery/data_source_google_bigquery_table.go
@@ -0,0 +1,51 @@
+package bigquery
+
+import (
+	"fmt"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-provider-google/google/tpgresource"
+	transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
+)
+
+func DataSourceGoogleBigQueryTable() *schema.Resource {
+	dsSchema := tpgresource.DatasourceSchemaFromResourceSchema(ResourceBigQueryTable().Schema)
+
+	tpgresource.AddRequiredFieldsToSchema(dsSchema, "dataset_id")
+	tpgresource.AddRequiredFieldsToSchema(dsSchema, "table_id")
+	tpgresource.AddOptionalFieldsToSchema(dsSchema, "project")
+
+	return &schema.Resource{
+		Read:   dataSourceBigQueryTableRead,
+		Schema: dsSchema,
+	}
+}
+
+func dataSourceBigQueryTableRead(d *schema.ResourceData, meta interface{}) error {
+	config := meta.(*transport_tpg.Config)
+	project, err := tpgresource.GetProject(d, config)
+	if err != nil {
+		return fmt.Errorf("Error fetching project: %s", err)
+	}
+
+	datasetID := d.Get("dataset_id").(string)
+	tableID := d.Get("table_id").(string)
+
+	id := fmt.Sprintf("projects/%s/datasets/%s/tables/%s", project, datasetID, tableID)
+	d.SetId(id)
+
+	err = resourceBigQueryTableRead(d, meta)
+	if err != nil {
+		return fmt.Errorf("Error retrieving table: %s", err)
+	}
+
+	if err := tpgresource.SetDataSourceLabels(d); err != nil {
+		return err
+	}
+
+	if d.Id() == "" {
+		return fmt.Errorf("%s not found", id)
+	}
+
+	return nil
+}
diff --git a/mmv1/third_party/terraform/services/bigquery/data_source_google_bigquery_table_test.go b/mmv1/third_party/terraform/services/bigquery/data_source_google_bigquery_table_test.go
new file mode 100644
index 000000000000..906978facf6b
--- /dev/null
+++ b/mmv1/third_party/terraform/services/bigquery/data_source_google_bigquery_table_test.go
@@ -0,0 +1,190 @@
+package bigquery_test
+
+import (
+	"encoding/json"
+	"fmt"
+	"regexp"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+	"github.com/hashicorp/terraform-provider-google/google/acctest"
+	"github.com/hashicorp/terraform-provider-google/google/envvar"
+)
+
+func TestAccDataSourceGoogleBigqueryTable_basic(t *testing.T) {
+	t.Parallel()
+
+	context := map[string]interface{}{
+		"random_suffix": acctest.RandString(t, 10),
+	}
+
+	expectedID := fmt.Sprintf("projects/%s/datasets/%s/tables/%s", envvar.GetTestProjectFromEnv(), fmt.Sprintf("tf_test_ds_%s", context["random_suffix"]), fmt.Sprintf("tf_test_table_%s", context["random_suffix"]))
+
+	acctest.VcrTest(t, resource.TestCase{
+		PreCheck:                 func() { acctest.AccTestPreCheck(t) },
+		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
+		CheckDestroy:             testAccCheckBigQueryTableDestroyProducer(t),
+		Steps: []resource.TestStep{
+			{
+				Config: testAccDataSourceGoogleBigqueryTable_basic(context),
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttr("data.google_bigquery_table.example", "table_id", fmt.Sprintf("tf_test_table_%s", context["random_suffix"])),
+					resource.TestCheckResourceAttr("data.google_bigquery_table.example", "dataset_id", fmt.Sprintf("tf_test_ds_%s", context["random_suffix"])),
+					resource.TestCheckResourceAttrSet("data.google_bigquery_table.example", "schema"),
+					resource.TestCheckResourceAttr("data.google_bigquery_table.example", "id", expectedID),
+					resource.TestCheckResourceAttrWith("data.google_bigquery_table.example", "schema", func(schema string) error {
+						var parsedSchema []map[string]interface{}
+
+						if err := json.Unmarshal([]byte(schema), &parsedSchema); err != nil {
+							return fmt.Errorf("failed to parse schema JSON: %w", err)
+						}
+
+						if len(parsedSchema) > 0 {
+							if parsedSchema[0]["name"] != "name" {
+								return fmt.Errorf("expected fields[0].name to be 'name', got '%v'", parsedSchema[0]["name"])
+							}
+							if parsedSchema[0]["type"] != "STRING" {
+								return fmt.Errorf("expected fields[0].type to be 'STRING', got '%v'", parsedSchema[0]["type"])
+							}
+							if parsedSchema[0]["mode"] != "NULLABLE" {
+								return fmt.Errorf("expected fields[0].mode to be 'NULLABLE', got '%v'", parsedSchema[0]["mode"])
+							}
+						}
+
+						if len(parsedSchema) > 2 {
+							if parsedSchema[2]["name"] != "address" {
+								return fmt.Errorf("expected fields[2].name to be 'address', got '%v'", parsedSchema[2]["name"])
+							}
+							if subFields, ok := parsedSchema[2]["fields"].([]interface{}); ok && len(subFields) > 1 {
+								subField := subFields[1].(map[string]interface{})
+								if subField["name"] != "zip" {
+									return fmt.Errorf("expected fields[2].fields[1].name to be 'zip', got '%v'", subField["name"])
+								}
+							}
+						}
+
+						if len(parsedSchema) > 4 {
+							if parsedSchema[4]["name"] != "policy_tag_test" {
+								return fmt.Errorf("expected fields[4].name to be 'policy_tag_test', got '%v'", parsedSchema[4]["name"])
+							}
+							if policyTags, ok := parsedSchema[4]["policyTags"].(map[string]interface{}); ok {
+								if names, ok := policyTags["names"].([]interface{}); ok && len(names) > 0 {
+									if !regexp.MustCompile("^projects/[^/]+/locations/us-central1/taxonomies/[^/]+/policyTags/[^/]+$").MatchString(names[0].(string)) {
+										return fmt.Errorf("policy tag does not match expected pattern")
+									}
+								}
+							}
+						}
+
+						return nil
+					}),
+				),
+			},
+		},
+	})
+}
+
+func testAccDataSourceGoogleBigqueryTable_basic(context map[string]interface{}) string {
+	return acctest.Nprintf(`
+
+  resource "google_data_catalog_policy_tag" "test" {
+    taxonomy     = google_data_catalog_taxonomy.test.id
+    display_name = "Low security"
+    description  = "A policy tag normally associated with low security items"
+  }
+  
+  resource "google_data_catalog_taxonomy" "test" {
+    region                 = "us-central1"
+    display_name           = "taxonomy_%{random_suffix}"
+    description            = "A collection of policy tags"
+    activated_policy_types = ["FINE_GRAINED_ACCESS_CONTROL"]
+  }
+
+  resource "google_bigquery_dataset" "test" {
+    dataset_id                  = "tf_test_ds_%{random_suffix}"
+    friendly_name               = "testing"
+    description                 = "This is a test description"
+    location                    = "us-central1"
+    default_table_expiration_ms = 3600000
+  }
+
+  resource "google_bigquery_table" "test" {
+    dataset_id          = google_bigquery_dataset.test.dataset_id
+    table_id            = "tf_test_table_%{random_suffix}"
+    deletion_protection = false
+    depends_on          = [google_data_catalog_policy_tag.test]
+    schema              = <<EOF
+    [
+      {
+        "name": "name",
+        "type": "STRING",
+        "mode": "NULLABLE"
+      },
+	  {
+		"name": "age",
+		"type": "INTEGER",
+		"mode": "NULLABLE",
+		"description": "Age of the person"
+	  },
+	  {
+		"name": "address",
+		"type": "RECORD",
+		"mode": "NULLABLE",
+		"fields": [
+		  {
+			"name": "street",
+			"type": "STRING",
+			"mode": "NULLABLE"
+		  },
+		  {
+			"name": "zip",
+			"type": "STRING",
+			"mode": "NULLABLE"
+		  },
+		  {
+			"name": "city",
+			"type": "STRING",
+			"mode": "NULLABLE"
+		  }
+		],
+		"description": "Address of the person"
+      },
+	  {
+		"name": "phone_numbers",
+		"type": "RECORD",
+		"mode": "REPEATED",
+		"fields": [
+		  {
+			"name": "type",
+			"type": "STRING",
+			"mode": "NULLABLE"
+		  },
+		  {
+			"name": "number",
+			"type": "STRING",
+			"mode": "NULLABLE"
+		  }
+		],
+		"description": "Phone numbers of the person"
+	  },
+	  {
+		"name": "policy_tag_test",
+		"type": "STRING",
+		"mode": "NULLABLE",
+		"description": "A test field with policy tags",
+		"policyTags": {
+		  "names": [
+			"${google_data_catalog_policy_tag.test.id}"
+          ]
+		}
+      }
+    ]
+    EOF
+  }
+
+  data "google_bigquery_table" "example" {
+    dataset_id = google_bigquery_table.test.dataset_id
+	table_id   = google_bigquery_table.test.table_id
+  }
+`, context)
+}
diff --git a/mmv1/third_party/terraform/website/docs/d/bigquery_table.html.markdown b/mmv1/third_party/terraform/website/docs/d/bigquery_table.html.markdown
new file mode 100644
index 000000000000..891ad627d379
--- /dev/null
+++ b/mmv1/third_party/terraform/website/docs/d/bigquery_table.html.markdown
@@ -0,0 +1,36 @@
+---
+subcategory: "BigQuery"
+description: |-
+  A datasource to retrieve a specific table in a dataset.
+---
+
+# `google_bigquery_table`
+
+Get a specific table in a BigQuery dataset. For more information see
+the [official documentation](https://cloud.google.com/bigquery/docs)
+and [API](https://cloud.google.com/bigquery/docs/reference/rest/v2/tables/get).
+
+## Example Usage
+
+```hcl
+data "google_bigquery_table" "table" {
+  project    = "my-project"
+  dataset_id = "my-bq-dataset"
+  table_id   = "my-table"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `dataset_id` - (Required) The dataset ID.
+
+* `table_id` - (Required) The table ID.
+
+* `project` - (Optional) The ID of the project in which the resource belongs.
+  If it is not provided, the provider project is used.
+
+## Attributes Reference
+
+See [google_bigquery_table](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_table#attributes-reference) resource for details of the available attributes.


Reviews:
user: login: c2thorn
body: thanks for the contribution @ramonvermeulen, this looks good to me.
Did you find the documentation to do this sufficient? https://googlecloudplatform.github.io/magic-modules/develop/add-handwritten-datasource/
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-22T19:30:36Z
user: login: ramonvermeulen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T20:30:30Z

Review comments:
diff_hunk: @@ -0,0 +1,190 @@
+package bigquery_test
+
+import (
+	"encoding/json"
+	"fmt"
+	"regexp"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+	"github.com/hashicorp/terraform-provider-google/google/acctest"
+	"github.com/hashicorp/terraform-provider-google/google/envvar"
+)
+
+func TestAccDataSourceGoogleBigqueryTable_basic(t *testing.T) {
+	t.Parallel()
+
+	context := map[string]interface{}{
+		"random_suffix": acctest.RandString(t, 10),
+	}
+
+	expectedID := fmt.Sprintf("projects/%s/datasets/%s/tables/%s", envvar.GetTestProjectFromEnv(), fmt.Sprintf("tf_test_ds_%s", context["random_suffix"]), fmt.Sprintf("tf_test_table_%s", context["random_suffix"]))
+
+	acctest.VcrTest(t, resource.TestCase{
+		PreCheck:                 func() { acctest.AccTestPreCheck(t) },
+		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
+		CheckDestroy:             testAccCheckBigQueryTableDestroyProducer(t),
+		Steps: []resource.TestStep{
+			{
+				Config: testAccDataSourceGoogleBigqueryTable_basic(context),
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttr("data.google_bigquery_table.example", "table_id", fmt.Sprintf("tf_test_table_%s", context["random_suffix"])),
+					resource.TestCheckResourceAttr("data.google_bigquery_table.example", "dataset_id", fmt.Sprintf("tf_test_ds_%s", context["random_suffix"])),
+					resource.TestCheckResourceAttrSet("data.google_bigquery_table.example", "schema"),
+					resource.TestCheckResourceAttr("data.google_bigquery_table.example", "id", expectedID),
+					resource.TestCheckResourceAttrWith("data.google_bigquery_table.example", "schema", func(schema string) error {
path: mmv1/third_party/terraform/services/bigquery/data_source_google_bigquery_table_test.go
user: login: c2thorn
body: prefer this to be its own function below for readability, but this is something very easily reformatted by us/someone else later.
created_at: 2025-05-22T19:28:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,190 @@
+package bigquery_test
+
+import (
+	"encoding/json"
+	"fmt"
+	"regexp"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+	"github.com/hashicorp/terraform-provider-google/google/acctest"
+	"github.com/hashicorp/terraform-provider-google/google/envvar"
+)
+
+func TestAccDataSourceGoogleBigqueryTable_basic(t *testing.T) {
+	t.Parallel()
+
+	context := map[string]interface{}{
+		"random_suffix": acctest.RandString(t, 10),
+	}
+
+	expectedID := fmt.Sprintf("projects/%s/datasets/%s/tables/%s", envvar.GetTestProjectFromEnv(), fmt.Sprintf("tf_test_ds_%s", context["random_suffix"]), fmt.Sprintf("tf_test_table_%s", context["random_suffix"]))
+
+	acctest.VcrTest(t, resource.TestCase{
+		PreCheck:                 func() { acctest.AccTestPreCheck(t) },
+		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
+		CheckDestroy:             testAccCheckBigQueryTableDestroyProducer(t),
+		Steps: []resource.TestStep{
+			{
+				Config: testAccDataSourceGoogleBigqueryTable_basic(context),
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttr("data.google_bigquery_table.example", "table_id", fmt.Sprintf("tf_test_table_%s", context["random_suffix"])),
+					resource.TestCheckResourceAttr("data.google_bigquery_table.example", "dataset_id", fmt.Sprintf("tf_test_ds_%s", context["random_suffix"])),
+					resource.TestCheckResourceAttrSet("data.google_bigquery_table.example", "schema"),
+					resource.TestCheckResourceAttr("data.google_bigquery_table.example", "id", expectedID),
+					resource.TestCheckResourceAttrWith("data.google_bigquery_table.example", "schema", func(schema string) error {
path: mmv1/third_party/terraform/services/bigquery/data_source_google_bigquery_table_test.go
user: login: ramonvermeulen
body: Check, makes sense!
created_at: 2025-05-22T20:30:30Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-20T10:25:30Z
body: Hello! I am a robot. Tests will require approval from a repository maintainer to run. 

**Googlers:** For automatic test runs see go/terraform-auto-test-runs.

@c2thorn, a repository maintainer, has been assigned to [review your changes](https://googlecloudplatform.github.io/magic-modules/contribute/review-pr/). If you have not received review feedback within 2 business days, please leave a comment on this PR asking them to take a look.

You can help make sure that review is quick by [doing a self-review](https://googlecloudplatform.github.io/magic-modules/contribute/review-pr/) and by [running impacted tests locally](https://googlecloudplatform.github.io/magic-modules/get-started/run-provider-tests/).

author_association: NONE
user: login: ramonvermeulen
created_at: 2025-05-20T17:46:10Z
body: After final changes:

```
➜ make testacc TEST=./google/services/bigquery TESTARGS='-run=TestAccDataSourceGoogleBigqueryTable_basic'

TF_ACC=1 TF_SCHEMA_PANIC_ON_ERROR=1 go test ./google/services/bigquery -v -run=TestAccDataSourceGoogleBigqueryTable_basic -timeout 240m -ldflags="-X=github.com/hashicorp/terraform-provider-google/version.ProviderVersion=acc"
=== RUN   TestAccDataSourceGoogleBigqueryTable_basic
=== PAUSE TestAccDataSourceGoogleBigqueryTable_basic
=== CONT  TestAccDataSourceGoogleBigqueryTable_basic
--- PASS: TestAccDataSourceGoogleBigqueryTable_basic (13.03s)
PASS
ok      github.com/hashicorp/terraform-provider-google/google/services/bigquery 13.741s
```
author_association: CONTRIBUTOR
user: login: modular-magician
created_at: 2025-05-22T17:04:02Z
body: Hi there, I'm the Modular magician. I've detected the following information about your changes:

## Diff report

Your PR generated some diffs in downstreams - here they are.

`google` provider: [Diff](https://github.com/modular-magician/terraform-provider-google/compare/ae35ab603f9381ae135f41e40328d52fbb9ae576..c7e51b15e71eb8a81a862f3b45315eb45d1c34f4) ( 4 files changed, 324 insertions(+))
`google-beta` provider: [Diff](https://github.com/modular-magician/terraform-provider-google-beta/compare/1a8d411f491a2a901160cd95a2d1a2072d2d9779..1542f89e4d71b3f471868caad0436ea5eccbac5f) ( 4 files changed, 324 insertions(+))





author_association: COLLABORATOR
user: login: modular-magician
created_at: 2025-05-22T17:09:10Z
body: #### Tests analytics
Total tests: 134
Passed tests: 123
Skipped tests: 10
Affected tests: 1

<details>
<summary>Click here to see the affected service packages</summary>
<blockquote>

<ul>
<li>bigquery</li>

</ul>

</blockquote>
</details>
#### Action taken
<details>
<summary>Found 1 affected test(s) by replaying old test recordings. Starting RECORDING based on the most recent commit. Click here to see the affected tests
</summary>
<blockquote>
<ul>
<li>TestAccDataSourceGoogleBigqueryTable_basic</li>

</ul>
</blockquote>
</details>

[Get to know how VCR tests work](https://googlecloudplatform.github.io/magic-modules/develop/test/test/)
author_association: COLLABORATOR
user: login: modular-magician
created_at: 2025-05-22T17:09:52Z
body: 🟢 Tests passed during RECORDING mode:
`TestAccDataSourceGoogleBigqueryTable_basic` [[Debug log](https://storage.cloud.google.com/ci-vcr-logs/beta/refs/heads/auto-pr-14026/artifacts/b7b0361e-b41d-4c4b-ad05-ab252fdb5111/recording/TestAccDataSourceGoogleBigqueryTable_basic.log)]

🟢 No issues found for passed tests after REPLAYING rerun.

---






🟢 All tests passed!

View the [build log](https://storage.cloud.google.com/ci-vcr-logs/beta/refs/heads/auto-pr-14026/artifacts/b7b0361e-b41d-4c4b-ad05-ab252fdb5111/build-log/recording_test.log) or the [debug log](https://console.cloud.google.com/storage/browser/ci-vcr-logs/beta/refs/heads/auto-pr-14026/artifacts/b7b0361e-b41d-4c4b-ad05-ab252fdb5111/recording) for each test
author_association: COLLABORATOR
user: login: ramonvermeulen
created_at: 2025-05-22T20:29:24Z
body: > thanks for the contribution @ramonvermeulen, this looks good to me. Did you find the documentation to do this sufficient? https://googlecloudplatform.github.io/magic-modules/develop/add-handwritten-datasource/

@c2thorn 

Thanks for your review!

Yes, I think in general the mmv1 documentation is in quite a good shape. Maybe if it needs to improved, at point 2 I would add something related to the existence  of `tpgresource.DatasourceSchemaFromResourceSchema`. For a lot of `data` sources I think this is really useful, unless you want a different Schema than the original `resource`.
author_association: CONTRIBUTOR