You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by ze...@apache.org on 2026/06/12 18:48:50 UTC
(terraform-provider-iceberg) branch main updated: Add data source table (#41)
This is an automated email from the ASF dual-hosted git repository.
zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/terraform-provider-iceberg.git
The following commit(s) were added to refs/heads/main by this push:
new e95ff7c Add data source table (#41)
e95ff7c is described below
commit e95ff7ce0c7cb9b51ba5c8dcf4217e7fda8d469a
Author: Dao Thanh Tung <tt...@accountancy.smu.edu.sg>
AuthorDate: Fri Jun 12 19:48:45 2026 +0100
Add data source table (#41)
* Add data source table
Signed-off-by: dttung2905 <tt...@accountancy.smu.edu.sg>
* Add docs
Signed-off-by: dttung2905 <tt...@accountancy.smu.edu.sg>
* fixes comments from code review
Signed-off-by: dttung2905 <tt...@accountancy.smu.edu.sg>
* Fix docs
Signed-off-by: dttung2905 <tt...@accountancy.smu.edu.sg>
* rename sync to convert
Signed-off-by: dttung2905 <tt...@accountancy.smu.edu.sg>
---------
Signed-off-by: dttung2905 <tt...@accountancy.smu.edu.sg>
---
Makefile | 4 +
README.md | 9 +-
docs/data-sources/table.md | 100 +++++
docs/index.md | 8 +
examples/data-sources/iceberg_table/data-source.tf | 57 +++
internal/provider/data_source_table.go | 214 +++++++++++
internal/provider/data_source_table_test.go | 191 ++++++++++
internal/provider/provider.go | 4 +-
internal/provider/resource_table.go | 246 +-----------
internal/provider/table_metadata_convert.go | 86 +++++
internal/provider/table_tfschema.go | 424 +++++++++++++++++++++
11 files changed, 1103 insertions(+), 240 deletions(-)
diff --git a/Makefile b/Makefile
index c131a6d..12ae734 100644
--- a/Makefile
+++ b/Makefile
@@ -15,6 +15,10 @@
.PHONY: test-integration test-integration-setup test-integration-exec test-integration-cleanup
+test-integration: ## Start Docker services, run integration tests, then tear down
+ @$(MAKE) test-integration-setup
+ @EXIT=0; $(MAKE) test-integration-exec || EXIT=$$?; $(MAKE) test-integration-cleanup; exit $$EXIT
+
test-integration-setup: ## Start Docker services for integration tests
docker compose -f dev/docker-compose.yml kill
docker compose -f dev/docker-compose.yml rm -f
diff --git a/README.md b/README.md
index de88efd..e4d0e8d 100644
--- a/README.md
+++ b/README.md
@@ -19,12 +19,17 @@
This [Terraform](https://terraform.io) and [OpenTofu](https://www.opentofu.org/) provider allows you to manage Iceberg resources, such as namespaces and tables.
-## Supported Resources
+## Supported Data Sources
+
+- `iceberg_table`: Read metadata for an existing table (schema, partition spec, sort order, and catalog properties).
-The provider currently supports the following resources:
+## Supported Resources
- `iceberg_namespace`: Manage Iceberg namespaces and their properties.
- `iceberg_table`: Manage Iceberg tables, including schema definitions and properties.
+- `iceberg_polaris_principal`: Manage Polaris principals and client credentials (when using a Polaris catalog).
+
+See the [provider documentation](docs/index.md) for examples and attribute reference.
## Local Development
diff --git a/docs/data-sources/table.md b/docs/data-sources/table.md
new file mode 100644
index 0000000..de040d4
--- /dev/null
+++ b/docs/data-sources/table.md
@@ -0,0 +1,100 @@
+---
+page_title: "iceberg_table Data Source - Iceberg"
+subcategory: ""
+description: |-
+ Reads metadata for an existing Iceberg table from the catalog.
+---
+
+<!--
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ - KIND, either express or implied. See the License for the
+ - specific language governing permissions and limitations
+ - under the License.
+ -->
+
+# iceberg_table (Data Source)
+
+Reads metadata for an existing Iceberg table from the catalog.
+
+Use this data source to inspect tables created outside Terraform, to reference tables managed in another workspace, or to read the current catalog state of a table also managed by an [`iceberg_table` resource](../resources/table.md).
+
+## Example Usage
+
+### Read a table by namespace and name
+
+```terraform
+data "iceberg_table" "events" {
+ namespace = ["analytics", "prod"]
+ name = "events"
+}
+
+output "events_table_id" {
+ value = data.iceberg_table.events.id
+}
+
+output "events_id_column" {
+ value = data.iceberg_table.events.schema.fields[0].name
+}
+```
+
+### Read a table managed by this provider
+
+```terraform
+resource "iceberg_namespace" "example" {
+ name = ["example_namespace"]
+}
+
+resource "iceberg_table" "example" {
+ namespace = iceberg_namespace.example.name
+ name = "example_table"
+
+ schema = {
+ fields = [
+ {
+ name = "id"
+ type = "long"
+ required = true
+ }
+ ]
+ }
+}
+
+data "iceberg_table" "example" {
+ depends_on = [iceberg_table.example]
+
+ namespace = iceberg_namespace.example.name
+ name = iceberg_table.example.name
+}
+```
+
+## Schema
+
+### Required
+
+- `name` (String) The name of the table.
+- `namespace` (List of String) The namespace of the table.
+
+### Read-Only
+
+- `id` (String) Dot-separated full table identifier (namespace segments and table name).
+- `partition_spec` (Attributes) The partition spec of the table. Empty when the table is not partitioned. Nested field shapes match the [`iceberg_table` resource](../resources/table.md#nestedatt--partition_spec).
+- `schema` (Attributes) The current schema of the table. Nested field shapes match the [`iceberg_table` resource](../resources/table.md#nestedatt--schema); all nested attributes are computed on read.
+- `server_properties` (Map of String) Properties from table metadata as returned by the catalog (the same values surfaced as `server_properties` on the table resource after apply).
+- `sort_order` (Attributes) The sort order of the table. Empty when the table is unsorted. Nested field shapes match the [`iceberg_table` resource](../resources/table.md#nestedatt--sort_order).
+
+## Error handling
+
+- If the table does not exist, apply fails with a **Table not found** error.
+- If `catalog_uri` is not configured on the provider, apply fails with **Catalog not available**.
diff --git a/docs/index.md b/docs/index.md
index 97c104d..ca107f8 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -27,7 +27,15 @@ description: |-
Use Terraform to interact with Iceberg REST Catalog instances.
+## Data Sources
+- [iceberg_table](data-sources/table.md) — Read metadata for an existing table from the catalog.
+
+## Resources
+
+- [iceberg_namespace](resources/namespace.md) — Manage a catalog namespace.
+- [iceberg_table](resources/table.md) — Manage an Iceberg table.
+- [iceberg_polaris_principal](resources/polaris_principal.md) — Manage a Polaris principal (Polaris deployments).
## Schema
diff --git a/examples/data-sources/iceberg_table/data-source.tf b/examples/data-sources/iceberg_table/data-source.tf
new file mode 100644
index 0000000..bd03d79
--- /dev/null
+++ b/examples/data-sources/iceberg_table/data-source.tf
@@ -0,0 +1,57 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+resource "iceberg_namespace" "example" {
+ name = ["example_namespace"]
+}
+
+resource "iceberg_table" "example" {
+ namespace = iceberg_namespace.example.name
+ name = "example_table"
+
+ schema = {
+ fields = [
+ {
+ name = "id"
+ type = "long"
+ required = true
+ },
+ {
+ name = "data"
+ type = "string"
+ required = false
+ }
+ ]
+ }
+}
+
+data "iceberg_table" "example" {
+ depends_on = [iceberg_table.example]
+
+ namespace = iceberg_namespace.example.name
+ name = iceberg_table.example.name
+}
+
+output "table_id" {
+ value = data.iceberg_table.example.id
+}
+
+output "table_schema_field_names" {
+ value = [for f in data.iceberg_table.example.schema.fields : f.name]
+}
+
+output "table_server_properties" {
+ value = data.iceberg_table.example.server_properties
+}
diff --git a/internal/provider/data_source_table.go b/internal/provider/data_source_table.go
new file mode 100644
index 0000000..a26b38e
--- /dev/null
+++ b/internal/provider/data_source_table.go
@@ -0,0 +1,214 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package provider
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/apache/iceberg-go/catalog"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ dschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+var _ datasource.DataSource = &icebergTableDataSource{}
+
+func NewTableDataSource() datasource.DataSource {
+ return &icebergTableDataSource{}
+}
+
+type icebergTableDataSourceModel struct {
+ ID types.String `tfsdk:"id"`
+ Namespace types.List `tfsdk:"namespace"`
+ Name types.String `tfsdk:"name"`
+ Schema types.Object `tfsdk:"schema"`
+ PartitionSpec types.Object `tfsdk:"partition_spec"`
+ SortOrder types.Object `tfsdk:"sort_order"`
+ ServerProperties types.Map `tfsdk:"server_properties"`
+}
+
+type icebergTableDataSource struct {
+ catalog catalog.Catalog
+ provider *icebergProvider
+}
+
+func (d *icebergTableDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_table"
+}
+
+func (d *icebergTableDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = dschema.Schema{
+ Description: "Reads metadata for an existing Iceberg table from the catalog.",
+ Attributes: map[string]dschema.Attribute{
+ "id": dschema.StringAttribute{
+ Description: "Dot-separated full table identifier (namespace segments + name).",
+ Computed: true,
+ },
+ "namespace": dschema.ListAttribute{
+ Description: "The namespace of the table.",
+ Required: true,
+ ElementType: types.StringType,
+ },
+ "name": dschema.StringAttribute{
+ Description: "The name of the table.",
+ Required: true,
+ },
+ "schema": dschema.SingleNestedAttribute{
+ Description: "The current schema of the table.",
+ Computed: true,
+ Attributes: tableSchemaDataSourceAttributes(),
+ },
+ "partition_spec": dschema.SingleNestedAttribute{
+ Description: "The partition spec of the table; fields is empty when unpartitioned.",
+ Computed: true,
+ Attributes: tablePartitionSpecDataSourceAttributes(),
+ },
+ "sort_order": dschema.SingleNestedAttribute{
+ Description: "The sort order of the table; fields is empty when unsorted.",
+ Computed: true,
+ Attributes: tableSortOrderDataSourceAttributes(),
+ },
+ "server_properties": dschema.MapAttribute{
+ Description: "Properties from table metadata as returned by the catalog.",
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ },
+ }
+}
+
+func (d *icebergTableDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ provider, ok := req.ProviderData.(*icebergProvider)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ fmt.Sprintf("Expected *icebergProvider, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ d.provider = provider
+}
+
+func (d *icebergTableDataSource) configureCatalog(ctx context.Context, diags *diag.Diagnostics) {
+ if d.catalog != nil {
+ return
+ }
+
+ if d.provider == nil {
+ diags.AddError(
+ "Provider not configured",
+ "The provider hasn't been configured before this operation",
+ )
+
+ return
+ }
+
+ if d.provider.catalogURI == "" {
+ return
+ }
+
+ cat, err := d.provider.NewCatalog(ctx)
+ if err != nil {
+ diags.AddError(
+ "Failed to create catalog",
+ "Failed to create catalog: "+err.Error(),
+ )
+
+ return
+ }
+ d.catalog = cat
+}
+
+func (d *icebergTableDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ tflog.Info(ctx, "Reading iceberg_table data source")
+ d.configureCatalog(ctx, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ var data icebergTableDataSourceModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ if d.catalog == nil {
+ resp.Diagnostics.AddError(
+ "Catalog not available",
+ "The catalog could not be created (is catalog_uri set?).",
+ )
+
+ return
+ }
+
+ if data.Name.IsNull() || data.Name.IsUnknown() {
+ resp.Diagnostics.AddError(
+ "Invalid table name",
+ "The name attribute must be set to a non-null, known value.",
+ )
+
+ return
+ }
+
+ var namespaceName []string
+ resp.Diagnostics.Append(data.Namespace.ElementsAs(ctx, &namespaceName, false)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tableName := data.Name.ValueString()
+ tableIdent := append(namespaceName, tableName)
+
+ tbl, err := d.catalog.LoadTable(ctx, tableIdent)
+ if err != nil {
+ if errors.Is(err, catalog.ErrNoSuchTable) {
+ resp.Diagnostics.AddError(
+ "Table not found",
+ "No such table: "+strings.Join(tableIdent, "."),
+ )
+
+ return
+ }
+ resp.Diagnostics.AddError("failed to load table", err.Error())
+
+ return
+ }
+
+ data.ID = types.StringValue(strings.Join(tableIdent, "."))
+
+ meta := icebergTableMetadataFields{}
+ convertIcebergTableMetadataToModel(ctx, tbl, &meta, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ data.Schema = meta.Schema
+ data.PartitionSpec = meta.PartitionSpec
+ data.SortOrder = meta.SortOrder
+ data.ServerProperties = meta.ServerProperties
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/internal/provider/data_source_table_test.go b/internal/provider/data_source_table_test.go
new file mode 100644
index 0000000..917d18f
--- /dev/null
+++ b/internal/provider/data_source_table_test.go
@@ -0,0 +1,191 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package provider
+
+import (
+ "fmt"
+ "os"
+ "regexp"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+// TestAccIcebergTableDataSource_Full runs end-to-end against a real REST catalog when ICEBERG_CATALOG_URI is set
+// (same pattern as TestAccPolarisPrincipal_Full and the integration Makefile targets).
+func TestAccIcebergTableDataSource_Full(t *testing.T) {
+ catalogURI := os.Getenv("ICEBERG_CATALOG_URI")
+ if catalogURI == "" {
+ t.Skip("ICEBERG_CATALOG_URI not set, skipping table data source E2E test")
+ }
+
+ providerCfg := fmt.Sprintf(providerConfig, catalogURI)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccIcebergTableDataSourceBasicConfig(providerCfg, "ds_e2e_basic"),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "namespace.0", "db_table_ds_basic"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "name", "ds_e2e_basic"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "schema.fields.0.name", "id"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "schema.fields.0.type", "long"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "schema.fields.1.name", "data"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "schema.fields.1.type", "string"),
+ resource.TestCheckResourceAttrPair("data.iceberg_table.read", "id", "iceberg_table.subject", "id"),
+ resource.TestCheckResourceAttrSet("data.iceberg_table.read", "schema.id"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "partition_spec.fields.#", "0"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "sort_order.fields.#", "0"),
+ ),
+ },
+ {
+ Config: testAccIcebergTableDataSourcePartitionSortConfig(providerCfg, "ds_e2e_partition_sort", "bucket[16]", "asc"),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "name", "ds_e2e_partition_sort"),
+ resource.TestCheckResourceAttrPair("data.iceberg_table.read", "id", "iceberg_table.subject", "id"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "partition_spec.fields.#", "1"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "partition_spec.fields.0.source_ids.0", "1"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "partition_spec.fields.0.field_id", "1000"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "partition_spec.fields.0.name", "id_bucket"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "partition_spec.fields.0.transform", "bucket[16]"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "sort_order.fields.#", "1"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "sort_order.fields.0.source_id", "1"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "sort_order.fields.0.transform", "identity"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "sort_order.fields.0.direction", "asc"),
+ resource.TestCheckResourceAttr("data.iceberg_table.read", "sort_order.fields.0.null_order", "nulls-first"),
+ ),
+ },
+ },
+ })
+}
+
+// TestAccIcebergTableDataSource_TableNotFound expects a clear error when the table does not exist.
+func TestAccIcebergTableDataSource_TableNotFound(t *testing.T) {
+ catalogURI := os.Getenv("ICEBERG_CATALOG_URI")
+ if catalogURI == "" {
+ t.Skip("ICEBERG_CATALOG_URI not set, skipping table data source E2E test")
+ }
+
+ providerCfg := fmt.Sprintf(providerConfig, catalogURI)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccIcebergTableDataSourceMissingTableConfig(providerCfg),
+ ExpectError: regexp.MustCompile(`Table not found`),
+ },
+ },
+ })
+}
+
+func testAccIcebergTableDataSourceBasicConfig(providerCfg string, tableName string) string {
+ return providerCfg + fmt.Sprintf(`
+resource "iceberg_namespace" "db" {
+ name = ["db_table_ds_basic"]
+}
+
+resource "iceberg_table" "subject" {
+ namespace = iceberg_namespace.db.name
+ name = "%s"
+ schema = {
+ fields = [
+ {
+ id = 1
+ name = "id"
+ type = "long"
+ required = true
+ },
+ {
+ id = 2
+ name = "data"
+ type = "string"
+ required = false
+ }
+ ]
+ }
+}
+
+data "iceberg_table" "read" {
+ namespace = iceberg_namespace.db.name
+ name = iceberg_table.subject.name
+}
+`, tableName)
+}
+
+func testAccIcebergTableDataSourcePartitionSortConfig(providerCfg string, tableName string, partitionTransform string, sortDirection string) string {
+ return providerCfg + fmt.Sprintf(`
+resource "iceberg_namespace" "db" {
+ name = ["db_table_ds_part"]
+}
+
+resource "iceberg_table" "subject" {
+ namespace = iceberg_namespace.db.name
+ name = "%s"
+ schema = {
+ fields = [
+ {
+ id = 1
+ name = "id"
+ type = "long"
+ required = true
+ }
+ ]
+ }
+ partition_spec = {
+ fields = [
+ {
+ source_ids = [1]
+ field_id = 1000
+ name = "id_bucket"
+ transform = "%s"
+ }
+ ]
+ }
+ sort_order = {
+ fields = [
+ {
+ source_id = 1
+ transform = "identity"
+ direction = "%s"
+ null_order = "nulls-first"
+ }
+ ]
+ }
+}
+
+data "iceberg_table" "read" {
+ namespace = iceberg_namespace.db.name
+ name = iceberg_table.subject.name
+}
+`, tableName, partitionTransform, sortDirection)
+}
+
+func testAccIcebergTableDataSourceMissingTableConfig(providerCfg string) string {
+ return providerCfg + `
+resource "iceberg_namespace" "empty" {
+ name = ["db_table_ds_missing"]
+}
+
+data "iceberg_table" "missing" {
+ namespace = iceberg_namespace.empty.name
+ name = "definitely_no_such_table"
+}
+`
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index d165ce4..be73730 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -233,7 +233,9 @@ func (h *headerRoundTripper) RoundTrip(req *http.Request) (*http.Response, error
// DataSources defines the data sources implemented in the provider.
func (p *icebergProvider) DataSources(_ context.Context) []func() datasource.DataSource {
- return nil
+ return []func() datasource.DataSource{
+ NewTableDataSource,
+ }
}
// Resources defines the resources implemented in the provider.
diff --git a/internal/provider/resource_table.go b/internal/provider/resource_table.go
index 80171b8..de8c13e 100644
--- a/internal/provider/resource_table.go
+++ b/internal/provider/resource_table.go
@@ -24,7 +24,6 @@ import (
"github.com/apache/iceberg-go"
"github.com/apache/iceberg-go/catalog"
"github.com/apache/iceberg-go/table"
- "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -32,7 +31,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
- "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
@@ -92,98 +90,19 @@ func (r *icebergTableResource) Schema(_ context.Context, _ resource.SchemaReques
"schema": rscschema.SingleNestedAttribute{
Description: "The schema of the table.",
Required: true,
- Attributes: map[string]rscschema.Attribute{
- "id": rscschema.Int64Attribute{
- Description: "The schema ID.",
- Optional: true,
- Computed: true,
- },
- "fields": rscschema.ListNestedAttribute{
- Description: "The fields of the schema",
- Required: true,
- NestedObject: rscschema.NestedAttributeObject{
- Attributes: schemaFieldAttributes(4),
- },
- },
- },
+ Attributes: tableSchemaResourceAttributes(),
},
"partition_spec": rscschema.SingleNestedAttribute{
Description: "The partition spec of the table.",
Optional: true,
Computed: true,
- Attributes: map[string]rscschema.Attribute{
- "spec_id": rscschema.Int64Attribute{
- Description: "The partition spec ID.",
- Computed: true,
- },
- "fields": rscschema.ListNestedAttribute{
- Description: "The fields of the partition spec.",
- Required: true,
- NestedObject: rscschema.NestedAttributeObject{
- Attributes: map[string]rscschema.Attribute{
- "source_ids": rscschema.ListAttribute{
- Description: "The source field IDs.",
- Required: true,
- ElementType: types.Int64Type,
- },
- "field_id": rscschema.Int64Attribute{
- Description: "The partition field ID.",
- Optional: true,
- Computed: true,
- },
- "name": rscschema.StringAttribute{
- Description: "The partition field name.",
- Required: true,
- },
- "transform": rscschema.StringAttribute{
- Description: "The partition transform.",
- Required: true,
- },
- },
- },
- },
- },
+ Attributes: tablePartitionSpecResourceAttributes(),
},
"sort_order": rscschema.SingleNestedAttribute{
Description: "The sort order of the table.",
Optional: true,
Computed: true,
- Attributes: map[string]rscschema.Attribute{
- "order_id": rscschema.Int64Attribute{
- Description: "The sort order ID.",
- Computed: true,
- },
- "fields": rscschema.ListNestedAttribute{
- Description: "The fields of the sort order.",
- Required: true,
- NestedObject: rscschema.NestedAttributeObject{
- Attributes: map[string]rscschema.Attribute{
- "source_id": rscschema.Int64Attribute{
- Description: "The source field ID.",
- Required: true,
- },
- "transform": rscschema.StringAttribute{
- Description: "The sort transform.",
- Required: true,
- },
- "direction": rscschema.StringAttribute{
- Description: "The sort direction (asc or desc).",
- Required: true,
- Validators: []validator.String{
- stringvalidator.OneOf("asc", "desc"),
- },
- },
- "null_order": rscschema.StringAttribute{
- Description: "The null order (nulls-first or nulls-last).",
- Required: true,
- Validators: []validator.String{
- stringvalidator.OneOf("nulls-first", "nulls-last"),
- },
- },
- },
- },
- },
- },
+ Attributes: tableSortOrderResourceAttributes(),
},
"user_properties": rscschema.MapAttribute{
Description: "User-defined properties for the table.",
@@ -199,108 +118,6 @@ func (r *icebergTableResource) Schema(_ context.Context, _ resource.SchemaReques
}
}
-func schemaFieldAttributes(depth int) map[string]rscschema.Attribute {
- attrs := map[string]rscschema.Attribute{
- "id": rscschema.Int64Attribute{
- Description: "The field ID.",
- Optional: true,
- },
- "name": rscschema.StringAttribute{
- Description: "The field name.",
- Required: true,
- },
- "type": rscschema.StringAttribute{
- Description: "The field type (e.g., 'int', 'string', 'decimal(10,2)', 'struct'). For struct, use struct_properties.",
- Required: true,
- },
- "required": rscschema.BoolAttribute{
- Description: "Whether the field is required.",
- Required: true,
- },
- "doc": rscschema.StringAttribute{
- Description: "The field documentation.",
- Optional: true,
- },
- "list_properties": rscschema.SingleNestedAttribute{
- Description: "Properties for list type.",
- Optional: true,
- Attributes: map[string]rscschema.Attribute{
- "element_id": rscschema.Int64Attribute{
- Description: "The list element id.",
- Required: true,
- },
- "element_type": rscschema.StringAttribute{
- Description: "The list element type.",
- Required: true,
- },
- "element_required": rscschema.BoolAttribute{
- Description: "Whether the list element is required.",
- Required: true,
- },
- },
- },
- "map_properties": rscschema.SingleNestedAttribute{
- Description: "Properties for map type.",
- Optional: true,
- Attributes: map[string]rscschema.Attribute{
- "key_id": rscschema.Int64Attribute{
- Description: "The map key id.",
- Required: true,
- },
- "key_type": rscschema.StringAttribute{
- Description: "The map key type.",
- Required: true,
- },
- "value_id": rscschema.Int64Attribute{
- Description: "The map value id.",
- Required: true,
- },
- "value_type": rscschema.StringAttribute{
- Description: "The map value type.",
- Required: true,
- },
- "value_required": rscschema.BoolAttribute{
- Description: "Whether the map value is required.",
- Required: true,
- },
- },
- },
- }
-
- if depth > 0 {
- attrs["struct_properties"] = rscschema.SingleNestedAttribute{
- Description: "Properties for struct type.",
- Optional: true,
- Attributes: map[string]rscschema.Attribute{
- "fields": rscschema.ListNestedAttribute{
- Description: "The fields of the struct.",
- Required: true,
- NestedObject: rscschema.NestedAttributeObject{
- Attributes: schemaFieldAttributes(depth - 1),
- },
- },
- },
- }
- } else {
- // At max depth, we still need the attribute defined but it won't have fields
- attrs["struct_properties"] = rscschema.SingleNestedAttribute{
- Description: "Properties for struct type.",
- Optional: true,
- Attributes: map[string]rscschema.Attribute{
- "fields": rscschema.ListNestedAttribute{
- Description: "The fields of the struct.",
- Required: true,
- NestedObject: rscschema.NestedAttributeObject{
- Attributes: map[string]rscschema.Attribute{},
- },
- },
- },
- }
- }
-
- return attrs
-}
-
func (r *icebergTableResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
@@ -772,60 +589,15 @@ func (r *icebergTableResource) calculateSortOrderUpdates(ctx context.Context, pl
}
func (r *icebergTableResource) syncTableToModel(ctx context.Context, tbl *table.Table, model *icebergTableResourceModel, diags *diag.Diagnostics) {
- // Update ServerProperties
- serverProperties, d := types.MapValueFrom(ctx, types.StringType, tbl.Properties())
- diags.Append(d...)
+ meta := icebergTableMetadataFields{}
+ convertIcebergTableMetadataToModel(ctx, tbl, &meta, diags)
if diags.HasError() {
return
}
- model.ServerProperties = serverProperties
-
- // Update Schema from the table to capture any server-assigned IDs
- icebergSchema := tbl.Schema()
- var updatedSchema icebergTableSchema
- if err := updatedSchema.FromIceberg(icebergSchema); err != nil {
- diags.AddError("failed to convert iceberg schema to terraform schema", err.Error())
-
- return
- }
- var d2 diag.Diagnostics
- model.Schema, d2 = types.ObjectValueFrom(ctx, icebergTableSchema{}.AttrTypes(), updatedSchema)
- diags.Append(d2...)
- if diags.HasError() {
- return
- }
-
- // Update PartitionSpec
- icebergSpec := tbl.Spec()
- if icebergSpec.NumFields() > 0 {
- var updatedSpec icebergTablePartitionSpec
- if err := updatedSpec.FromIceberg(icebergSpec); err != nil {
- diags.AddError("failed to convert iceberg partition spec to terraform partition spec", err.Error())
-
- return
- }
- var d3 diag.Diagnostics
- model.PartitionSpec, d3 = types.ObjectValueFrom(ctx, icebergTablePartitionSpec{}.AttrTypes(), updatedSpec)
- diags.Append(d3...)
- } else {
- model.PartitionSpec = types.ObjectNull(icebergTablePartitionSpec{}.AttrTypes())
- }
-
- // Update SortOrder
- icebergOrder := tbl.SortOrder()
- if icebergOrder.Len() > 0 {
- var updatedOrder icebergTableSortOrder
- if err := updatedOrder.FromIceberg(icebergOrder); err != nil {
- diags.AddError("failed to convert iceberg sort order to terraform sort order", err.Error())
-
- return
- }
- var d4 diag.Diagnostics
- model.SortOrder, d4 = types.ObjectValueFrom(ctx, icebergTableSortOrder{}.AttrTypes(), updatedOrder)
- diags.Append(d4...)
- } else {
- model.SortOrder = types.ObjectNull(icebergTableSortOrder{}.AttrTypes())
- }
+ model.Schema = meta.Schema
+ model.PartitionSpec = meta.PartitionSpec
+ model.SortOrder = meta.SortOrder
+ model.ServerProperties = meta.ServerProperties
// Update UserProperties to match reality for tracked keys
if !model.UserProperties.IsNull() {
diff --git a/internal/provider/table_metadata_convert.go b/internal/provider/table_metadata_convert.go
new file mode 100644
index 0000000..2dd3a92
--- /dev/null
+++ b/internal/provider/table_metadata_convert.go
@@ -0,0 +1,86 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package provider
+
+import (
+ "context"
+
+ "github.com/apache/iceberg-go/table"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// icebergTableMetadataFields holds Terraform values converted from an Iceberg table's metadata
+// (schema, partition spec, sort order, properties). Used by the table resource and table data source.
+type icebergTableMetadataFields struct {
+ Schema types.Object
+ PartitionSpec types.Object
+ SortOrder types.Object
+ ServerProperties types.Map
+}
+
+func convertIcebergTableMetadataToModel(ctx context.Context, tbl *table.Table, out *icebergTableMetadataFields, diags *diag.Diagnostics) {
+ serverProperties, d := types.MapValueFrom(ctx, types.StringType, tbl.Properties())
+ diags.Append(d...)
+ if diags.HasError() {
+ return
+ }
+ out.ServerProperties = serverProperties
+
+ icebergSchema := tbl.Schema()
+ var updatedSchema icebergTableSchema
+ if err := updatedSchema.FromIceberg(icebergSchema); err != nil {
+ diags.AddError("failed to convert iceberg schema to terraform schema", err.Error())
+
+ return
+ }
+ var d2 diag.Diagnostics
+ out.Schema, d2 = types.ObjectValueFrom(ctx, icebergTableSchema{}.AttrTypes(), updatedSchema)
+ diags.Append(d2...)
+ if diags.HasError() {
+ return
+ }
+
+ icebergSpec := tbl.Spec()
+ if icebergSpec.NumFields() > 0 {
+ var updatedSpec icebergTablePartitionSpec
+ if err := updatedSpec.FromIceberg(icebergSpec); err != nil {
+ diags.AddError("failed to convert iceberg partition spec to terraform partition spec", err.Error())
+
+ return
+ }
+ var d3 diag.Diagnostics
+ out.PartitionSpec, d3 = types.ObjectValueFrom(ctx, icebergTablePartitionSpec{}.AttrTypes(), updatedSpec)
+ diags.Append(d3...)
+ } else {
+ out.PartitionSpec = types.ObjectNull(icebergTablePartitionSpec{}.AttrTypes())
+ }
+
+ icebergOrder := tbl.SortOrder()
+ if icebergOrder.Len() > 0 {
+ var updatedOrder icebergTableSortOrder
+ if err := updatedOrder.FromIceberg(icebergOrder); err != nil {
+ diags.AddError("failed to convert iceberg sort order to terraform sort order", err.Error())
+
+ return
+ }
+ var d4 diag.Diagnostics
+ out.SortOrder, d4 = types.ObjectValueFrom(ctx, icebergTableSortOrder{}.AttrTypes(), updatedOrder)
+ diags.Append(d4...)
+ } else {
+ out.SortOrder = types.ObjectNull(icebergTableSortOrder{}.AttrTypes())
+ }
+}
diff --git a/internal/provider/table_tfschema.go b/internal/provider/table_tfschema.go
new file mode 100644
index 0000000..8332194
--- /dev/null
+++ b/internal/provider/table_tfschema.go
@@ -0,0 +1,424 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package provider
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ dschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ rscschema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+const tableSchemaFieldMaxDepth = 4
+
+func tableSchemaResourceAttributes() map[string]rscschema.Attribute {
+ return map[string]rscschema.Attribute{
+ "id": rscschema.Int64Attribute{
+ Description: "The schema ID.",
+ Optional: true,
+ Computed: true,
+ },
+ "fields": rscschema.ListNestedAttribute{
+ Description: "The fields of the schema",
+ Required: true,
+ NestedObject: rscschema.NestedAttributeObject{
+ Attributes: tableSchemaFieldResourceAttributes(tableSchemaFieldMaxDepth),
+ },
+ },
+ }
+}
+
+func tableSchemaDataSourceAttributes() map[string]dschema.Attribute {
+ return map[string]dschema.Attribute{
+ "id": dschema.Int64Attribute{
+ Description: "The schema ID.",
+ Computed: true,
+ },
+ "fields": dschema.ListNestedAttribute{
+ Description: "The fields of the schema.",
+ Computed: true,
+ NestedObject: dschema.NestedAttributeObject{
+ Attributes: tableSchemaFieldDataSourceAttributes(tableSchemaFieldMaxDepth),
+ },
+ },
+ }
+}
+
+func tablePartitionSpecResourceAttributes() map[string]rscschema.Attribute {
+ return map[string]rscschema.Attribute{
+ "spec_id": rscschema.Int64Attribute{
+ Description: "The partition spec ID.",
+ Computed: true,
+ },
+ "fields": rscschema.ListNestedAttribute{
+ Description: "The fields of the partition spec.",
+ Required: true,
+ NestedObject: rscschema.NestedAttributeObject{
+ Attributes: tablePartitionSpecFieldResourceAttributes(),
+ },
+ },
+ }
+}
+
+func tablePartitionSpecDataSourceAttributes() map[string]dschema.Attribute {
+ return map[string]dschema.Attribute{
+ "spec_id": dschema.Int64Attribute{
+ Description: "The partition spec ID.",
+ Computed: true,
+ },
+ "fields": dschema.ListNestedAttribute{
+ Description: "The fields of the partition spec.",
+ Computed: true,
+ NestedObject: dschema.NestedAttributeObject{
+ Attributes: tablePartitionSpecFieldDataSourceAttributes(),
+ },
+ },
+ }
+}
+
+func tableSortOrderResourceAttributes() map[string]rscschema.Attribute {
+ return map[string]rscschema.Attribute{
+ "order_id": rscschema.Int64Attribute{
+ Description: "The sort order ID.",
+ Computed: true,
+ },
+ "fields": rscschema.ListNestedAttribute{
+ Description: "The fields of the sort order.",
+ Required: true,
+ NestedObject: rscschema.NestedAttributeObject{
+ Attributes: tableSortOrderFieldResourceAttributes(),
+ },
+ },
+ }
+}
+
+func tableSortOrderDataSourceAttributes() map[string]dschema.Attribute {
+ return map[string]dschema.Attribute{
+ "order_id": dschema.Int64Attribute{
+ Description: "The sort order ID.",
+ Computed: true,
+ },
+ "fields": dschema.ListNestedAttribute{
+ Description: "The fields of the sort order.",
+ Computed: true,
+ NestedObject: dschema.NestedAttributeObject{
+ Attributes: tableSortOrderFieldDataSourceAttributes(),
+ },
+ },
+ }
+}
+
+func tableSchemaFieldResourceAttributes(depth int) map[string]rscschema.Attribute {
+ attrs := map[string]rscschema.Attribute{
+ "id": rscschema.Int64Attribute{
+ Description: "The field ID.",
+ Optional: true,
+ },
+ "name": rscschema.StringAttribute{
+ Description: "The field name.",
+ Required: true,
+ },
+ "type": rscschema.StringAttribute{
+ Description: "The field type (e.g., 'int', 'string', 'decimal(10,2)', 'struct'). For struct, use struct_properties.",
+ Required: true,
+ },
+ "required": rscschema.BoolAttribute{
+ Description: "Whether the field is required.",
+ Required: true,
+ },
+ "doc": rscschema.StringAttribute{
+ Description: "The field documentation.",
+ Optional: true,
+ },
+ "list_properties": rscschema.SingleNestedAttribute{
+ Description: "Properties for list type.",
+ Optional: true,
+ Attributes: map[string]rscschema.Attribute{
+ "element_id": rscschema.Int64Attribute{
+ Description: "The list element id.",
+ Required: true,
+ },
+ "element_type": rscschema.StringAttribute{
+ Description: "The list element type.",
+ Required: true,
+ },
+ "element_required": rscschema.BoolAttribute{
+ Description: "Whether the list element is required.",
+ Required: true,
+ },
+ },
+ },
+ "map_properties": rscschema.SingleNestedAttribute{
+ Description: "Properties for map type.",
+ Optional: true,
+ Attributes: map[string]rscschema.Attribute{
+ "key_id": rscschema.Int64Attribute{
+ Description: "The map key id.",
+ Required: true,
+ },
+ "key_type": rscschema.StringAttribute{
+ Description: "The map key type.",
+ Required: true,
+ },
+ "value_id": rscschema.Int64Attribute{
+ Description: "The map value id.",
+ Required: true,
+ },
+ "value_type": rscschema.StringAttribute{
+ Description: "The map value type.",
+ Required: true,
+ },
+ "value_required": rscschema.BoolAttribute{
+ Description: "Whether the map value is required.",
+ Required: true,
+ },
+ },
+ },
+ }
+
+ if depth > 0 {
+ attrs["struct_properties"] = rscschema.SingleNestedAttribute{
+ Description: "Properties for struct type.",
+ Optional: true,
+ Attributes: map[string]rscschema.Attribute{
+ "fields": rscschema.ListNestedAttribute{
+ Description: "The fields of the struct.",
+ Required: true,
+ NestedObject: rscschema.NestedAttributeObject{
+ Attributes: tableSchemaFieldResourceAttributes(depth - 1),
+ },
+ },
+ },
+ }
+ } else {
+ attrs["struct_properties"] = rscschema.SingleNestedAttribute{
+ Description: "Properties for struct type.",
+ Optional: true,
+ Attributes: map[string]rscschema.Attribute{
+ "fields": rscschema.ListNestedAttribute{
+ Description: "The fields of the struct.",
+ Required: true,
+ NestedObject: rscschema.NestedAttributeObject{
+ Attributes: map[string]rscschema.Attribute{},
+ },
+ },
+ },
+ }
+ }
+
+ return attrs
+}
+
+func tableSchemaFieldDataSourceAttributes(depth int) map[string]dschema.Attribute {
+ attrs := map[string]dschema.Attribute{
+ "id": dschema.Int64Attribute{
+ Description: "The field ID.",
+ Computed: true,
+ },
+ "name": dschema.StringAttribute{
+ Description: "The field name.",
+ Computed: true,
+ },
+ "type": dschema.StringAttribute{
+ Description: "The field type (e.g., 'int', 'string', 'decimal(10,2)', 'struct'). For struct, use struct_properties.",
+ Computed: true,
+ },
+ "required": dschema.BoolAttribute{
+ Description: "Whether the field is required.",
+ Computed: true,
+ },
+ "doc": dschema.StringAttribute{
+ Description: "The field documentation.",
+ Computed: true,
+ },
+ "list_properties": dschema.SingleNestedAttribute{
+ Description: "Properties for list type.",
+ Computed: true,
+ Attributes: map[string]dschema.Attribute{
+ "element_id": dschema.Int64Attribute{
+ Description: "The list element id.",
+ Computed: true,
+ },
+ "element_type": dschema.StringAttribute{
+ Description: "The list element type.",
+ Computed: true,
+ },
+ "element_required": dschema.BoolAttribute{
+ Description: "Whether the list element is required.",
+ Computed: true,
+ },
+ },
+ },
+ "map_properties": dschema.SingleNestedAttribute{
+ Description: "Properties for map type.",
+ Computed: true,
+ Attributes: map[string]dschema.Attribute{
+ "key_id": dschema.Int64Attribute{
+ Description: "The map key id.",
+ Computed: true,
+ },
+ "key_type": dschema.StringAttribute{
+ Description: "The map key type.",
+ Computed: true,
+ },
+ "value_id": dschema.Int64Attribute{
+ Description: "The map value id.",
+ Computed: true,
+ },
+ "value_type": dschema.StringAttribute{
+ Description: "The map value type.",
+ Computed: true,
+ },
+ "value_required": dschema.BoolAttribute{
+ Description: "Whether the map value is required.",
+ Computed: true,
+ },
+ },
+ },
+ }
+
+ if depth > 0 {
+ attrs["struct_properties"] = dschema.SingleNestedAttribute{
+ Description: "Properties for struct type.",
+ Computed: true,
+ Attributes: map[string]dschema.Attribute{
+ "fields": dschema.ListNestedAttribute{
+ Description: "The fields of the struct.",
+ Computed: true,
+ NestedObject: dschema.NestedAttributeObject{
+ Attributes: tableSchemaFieldDataSourceAttributes(depth - 1),
+ },
+ },
+ },
+ }
+ } else {
+ attrs["struct_properties"] = dschema.SingleNestedAttribute{
+ Description: "Properties for struct type.",
+ Computed: true,
+ Attributes: map[string]dschema.Attribute{
+ "fields": dschema.ListNestedAttribute{
+ Description: "The fields of the struct.",
+ Computed: true,
+ NestedObject: dschema.NestedAttributeObject{
+ Attributes: map[string]dschema.Attribute{},
+ },
+ },
+ },
+ }
+ }
+
+ return attrs
+}
+
+func tablePartitionSpecFieldResourceAttributes() map[string]rscschema.Attribute {
+ return map[string]rscschema.Attribute{
+ "source_ids": rscschema.ListAttribute{
+ Description: "The source field IDs.",
+ Required: true,
+ ElementType: types.Int64Type,
+ },
+ "field_id": rscschema.Int64Attribute{
+ Description: "The partition field ID.",
+ Optional: true,
+ Computed: true,
+ },
+ "name": rscschema.StringAttribute{
+ Description: "The partition field name.",
+ Required: true,
+ },
+ "transform": rscschema.StringAttribute{
+ Description: "The partition transform.",
+ Required: true,
+ },
+ }
+}
+
+func tablePartitionSpecFieldDataSourceAttributes() map[string]dschema.Attribute {
+ return map[string]dschema.Attribute{
+ "source_ids": dschema.ListAttribute{
+ Description: "The source field IDs.",
+ Computed: true,
+ ElementType: types.Int64Type,
+ },
+ "field_id": dschema.Int64Attribute{
+ Description: "The partition field ID.",
+ Computed: true,
+ },
+ "name": dschema.StringAttribute{
+ Description: "The partition field name.",
+ Computed: true,
+ },
+ "transform": dschema.StringAttribute{
+ Description: "The partition transform.",
+ Computed: true,
+ },
+ }
+}
+
+func tableSortOrderFieldResourceAttributes() map[string]rscschema.Attribute {
+ return map[string]rscschema.Attribute{
+ "source_id": rscschema.Int64Attribute{
+ Description: "The source field ID.",
+ Required: true,
+ },
+ "transform": rscschema.StringAttribute{
+ Description: "The sort transform.",
+ Required: true,
+ },
+ "direction": rscschema.StringAttribute{
+ Description: "The sort direction (asc or desc).",
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("asc", "desc"),
+ },
+ },
+ "null_order": rscschema.StringAttribute{
+ Description: "The null order (nulls-first or nulls-last).",
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("nulls-first", "nulls-last"),
+ },
+ },
+ }
+}
+
+func tableSortOrderFieldDataSourceAttributes() map[string]dschema.Attribute {
+ return map[string]dschema.Attribute{
+ "source_id": dschema.Int64Attribute{
+ Description: "The source field ID.",
+ Computed: true,
+ },
+ "transform": dschema.StringAttribute{
+ Description: "The sort transform.",
+ Computed: true,
+ },
+ "direction": dschema.StringAttribute{
+ Description: "The sort direction (asc or desc).",
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("asc", "desc"),
+ },
+ },
+ "null_order": dschema.StringAttribute{
+ Description: "The null order (nulls-first or nulls-last).",
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("nulls-first", "nulls-last"),
+ },
+ },
+ }
+}