@@ -1368,6 +1368,12 @@ func ResourceBigQueryTable() *schema.Resource {
13681368 Description : `Whether Terraform will prevent implicitly added columns in schema from showing diff.` ,
13691369 },
13701370
1371+ "generated_schema_columns" : {
1372+ Type : schema .TypeString ,
1373+ Computed : true ,
1374+ Description : `(Output-only) A list of autogenerated schema fields.` ,
1375+ },
1376+
13711377 // TableConstraints: [Optional] Defines the primary key and foreign keys.
13721378 "table_constraints" : {
13731379 Type : schema .TypeList ,
@@ -1618,21 +1624,21 @@ func ResourceBigQueryTable() *schema.Resource {
16181624}
16191625
16201626// filterLiveSchemaByConfig compares a live schema from the BQ API with a schema from
1621- // the Terraform config. It returns a new schema containing only the fields
1622- // that are defined in the config, effectively removing any columns that were
1623- // auto-generated by the service (e.g., hive partitioning keys).
1624- //
1627+ // the Terraform config. It returns two values:
1628+ // 1. A new *bigquery.TableSchema containing a filtered list of fields that are defined in the config,
1629+ // effectively removing any columns that were auto-generated by the service (e.g., hive partitioning keys).
1630+ // 2. A slice of *bigquery.TableFieldSchema for the fields that were auto-generated by the service.
16251631// Parameters:
16261632// - liveSchema: The schema returned from a BigQuery API Read/Get call. This may contain extra columns.
16271633// - configSchema: The schema built from the user's Terraform configuration (`d.Get("schema")`). This is the source of truth.
1628- //
1629- // Returns:
1630- //
1631- // A new * bigquery.TableSchema containing a filtered list of fields.
1632- func filterLiveSchemaByConfig ( liveSchema * bigquery. TableSchema , configSchema * bigquery. TableSchema ) * bigquery. TableSchema {
1633- if liveSchema == nil || configSchema == nil {
1634- // If either schema is nil, there's nothing to compare, so return an empty schema .
1635- return & bigquery.TableSchema {Fields : []* bigquery.TableFieldSchema {}}
1634+ func filterLiveSchemaByConfig ( liveSchema * bigquery. TableSchema , configSchema * bigquery. TableSchema ) ( * bigquery. TableSchema , [] * bigquery. TableFieldSchema ) {
1635+ if liveSchema == nil {
1636+ // If live schema is nil, there's nothing to filter or collect.
1637+ return & bigquery.TableSchema { Fields : [] * bigquery. TableFieldSchema {}}, nil
1638+ }
1639+ if configSchema == nil || len ( configSchema . Fields ) == 0 {
1640+ // If config schema is nil or empty, all live fields are considered auto-generated .
1641+ return & bigquery.TableSchema {Fields : []* bigquery.TableFieldSchema {}}, liveSchema . Fields
16361642 }
16371643
16381644 // 1. Create a lookup map of all column names defined in the configuration.
@@ -1645,19 +1651,21 @@ func filterLiveSchemaByConfig(liveSchema *bigquery.TableSchema, configSchema *bi
16451651 // 2. Iterate through the fields in the live schema and keep only the ones
16461652 // that exist in our configuration map.
16471653 var filteredFields []* bigquery.TableFieldSchema
1654+ var autogeneratedFields []* bigquery.TableFieldSchema
16481655 for _ , liveField := range liveSchema .Fields {
16491656 // If the live field's name is present in the map of configured fields...
16501657 if _ , ok := configFieldsMap [liveField .Name ]; ok {
16511658 // ...then it's a field we care about. Add it to our filtered list.
16521659 filteredFields = append (filteredFields , liveField )
16531660 } else {
1654- log .Printf ("[DEBUG] auto-generated column `%s` dropped during Table read." , liveField .Name )
1661+ log .Printf ("[DEBUG] auto-generated column `%s` collected during Table read." , liveField .Name )
1662+ autogeneratedFields = append (autogeneratedFields , liveField )
16551663 }
16561664 }
16571665
16581666 return & bigquery.TableSchema {
16591667 Fields : filteredFields ,
1660- }
1668+ }, autogeneratedFields
16611669}
16621670
16631671func resourceTable (d * schema.ResourceData , meta interface {}) (* bigquery.Table , error ) {
@@ -2049,15 +2057,36 @@ func resourceBigQueryTableRead(d *schema.ResourceData, meta interface{}) error {
20492057 }
20502058
20512059 if res .Schema != nil {
2052- table , err := resourceTable (d , meta )
2053- if err != nil {
2054- return err
2060+ var configSchema * bigquery.TableSchema
2061+ if v , ok := d .GetOk ("schema" ); ok {
2062+ _ , viewPresent := d .GetOk ("view" )
2063+ _ , materializedViewPresent := d .GetOk ("materialized_view" )
2064+ managePolicyTags := ! viewPresent && ! materializedViewPresent
2065+ configSchema , err = expandSchema (v , managePolicyTags )
2066+ if err != nil {
2067+ return err
2068+ }
20552069 }
20562070
20572071 schemaFiltered := res .Schema
20582072 ignore , ok := d .Get ("ignore_auto_generated_schema" ).(bool )
20592073 if ok && ignore {
2060- schemaFiltered = filterLiveSchemaByConfig (res .Schema , table .Schema )
2074+ var autogeneratedFields []* bigquery.TableFieldSchema
2075+ schemaFiltered , autogeneratedFields = filterLiveSchemaByConfig (res .Schema , configSchema )
2076+ if len (autogeneratedFields ) > 0 {
2077+ autogeneratedFieldsJson , err := json .Marshal (autogeneratedFields )
2078+ if err != nil {
2079+ return fmt .Errorf ("error marshalling autogenerated schema fields: %w" , err )
2080+ }
2081+ if err := d .Set ("generated_schema_columns" , string (autogeneratedFieldsJson )); err != nil {
2082+ return fmt .Errorf ("error setting generated_schema_columns: %w" , err )
2083+ }
2084+ } else {
2085+ d .Set ("generated_schema_columns" , "" )
2086+ }
2087+ } else {
2088+ // If not ignoring, ensure the field is cleared
2089+ d .Set ("generated_schema_columns" , "" )
20612090 }
20622091 schema , err := flattenSchema (schemaFiltered )
20632092 if err != nil {
@@ -2144,6 +2173,25 @@ type TableReference struct {
21442173 tableID string
21452174}
21462175
2176+ func addAutoGenSchemaFields (d * schema.ResourceData , table * bigquery.Table ) error {
2177+ // When ignore_auto_generated_schema is true, we must include the autogenerated fields
2178+ // in the update payload to avoid the API thinking we're trying to delete them.
2179+ if ignore , enabled := d .Get ("ignore_auto_generated_schema" ).(bool ); enabled && ignore {
2180+ // Only proceed if the table has a schema to begin with.
2181+ if table .Schema != nil {
2182+ if autogenStr , ok := d .Get ("generated_schema_columns" ).(string ); ok && autogenStr != "" {
2183+ var autogenFields []* bigquery.TableFieldSchema
2184+ if err := json .Unmarshal ([]byte (autogenStr ), & autogenFields ); err != nil {
2185+ return fmt .Errorf ("failed to unmarshal autogenerated schema fields: %w" , err )
2186+ }
2187+ table .Schema .Fields = append (table .Schema .Fields , autogenFields ... )
2188+ log .Printf ("[DEBUG] Appended %d autogenerated fields to schema for update" , len (autogenFields ))
2189+ }
2190+ }
2191+ }
2192+ return nil
2193+ }
2194+
21472195func resourceBigQueryTableUpdate (d * schema.ResourceData , meta interface {}) error {
21482196 // If only client-side fields were modified, short-circuit the Update function to avoid sending an update API request.
21492197 clientSideFields := map [string ]bool {"deletion_protection" : true , "ignore_schema_changes" : true , "ignore_auto_generated_schema" : true , "table_metadata_view" : true }
@@ -2169,6 +2217,10 @@ func resourceBigQueryTableUpdate(d *schema.ResourceData, meta interface{}) error
21692217 return err
21702218 }
21712219
2220+ if err := addAutoGenSchemaFields (d , table ); err != nil {
2221+ return err
2222+ }
2223+
21722224 if table .ExternalDataConfiguration != nil && table .ExternalDataConfiguration .Schema != nil {
21732225 log .Printf ("[INFO] Removing ExternalDataConfiguration.Schema when updating BigQuery table %s" , d .Id ())
21742226 table .ExternalDataConfiguration .Schema = nil
0 commit comments