Skip to content

Preserve partitioning on temp FILE_LOADS tables#38833

Open
PRADDZY wants to merge 3 commits into
apache:masterfrom
PRADDZY:fix/beam-38017-partitioned-file-loads
Open

Preserve partitioning on temp FILE_LOADS tables#38833
PRADDZY wants to merge 3 commits into
apache:masterfrom
PRADDZY:fix/beam-38017-partitioned-file-loads

Conversation

@PRADDZY
Copy link
Copy Markdown

@PRADDZY PRADDZY commented Jun 5, 2026

Summary

  • preserve destination partitioning metadata when FILE_LOADS writes through temporary tables
  • reuse the destination table lookup for missing schema resolution and only inject partitioning when the caller did not already supply it
  • add regression coverage for inherited time/range partitioning and explicit partitioning precedence

Fixes #38017.

Testing

  • python -m pytest apache_beam/io/gcp/bigquery_file_loads_test.py -k "temporary_table_load_inherits_destination_time_partitioning or temporary_table_load_inherits_destination_range_partitioning or temporary_table_load_keeps_explicit_partitioning_parameters" -q
  • python -m pytest apache_beam/io/gcp/bigquery_file_loads_test.py::TestBigQueryFileLoads::test_wait_for_load_job_completion -q
  • python -m py_compile apache_beam/io/gcp/bigquery_file_loads.py apache_beam/io/gcp/bigquery_file_loads_test.py

@PRADDZY PRADDZY marked this pull request as ready for review June 7, 2026 18:09
Copilot AI review requested due to automatic review settings June 7, 2026 18:09
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds support for temporary-table BigQuery file loads to inherit partitioning settings from the final destination table when not explicitly provided, and validates this behavior with new unit tests.

Changes:

  • Add helpers to detect/propagate timePartitioning and rangePartitioning into load job parameters for temporary table loads.
  • Update TriggerLoadJobs.process to optionally fetch the destination table once to reuse schema and partitioning metadata.
  • Add tests verifying partitioning inheritance and that explicit partitioning parameters are not overridden.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
sdks/python/apache_beam/io/gcp/bigquery_file_loads.py Propagates destination-table partitioning settings into temporary-table load job parameters and reuses fetched table metadata.
sdks/python/apache_beam/io/gcp/bigquery_file_loads_test.py Adds unit tests for partitioning inheritance and explicit-parameter precedence.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +91 to +108
def _add_destination_partitioning_load_parameters(
additional_parameters, destination_table):
if not isinstance(destination_table, bigquery_tools.bigquery.Table):
return additional_parameters

additional_parameters = dict(additional_parameters)

if ('timePartitioning' not in additional_parameters and
getattr(destination_table, 'timePartitioning', None) is not None):
additional_parameters['timePartitioning'] = (
destination_table.timePartitioning)

if ('rangePartitioning' not in additional_parameters and
getattr(destination_table, 'rangePartitioning', None) is not None):
additional_parameters['rangePartitioning'] = (
destination_table.rangePartitioning)

return additional_parameters
Comment on lines +765 to +776
destination_table = None
hashed_dest = bigquery_tools.get_hashable_destination(table_reference)
should_lookup_destination_table = (
schema is None or
not _has_partitioning_load_parameters(additional_parameters))
if should_lookup_destination_table:
try:
destination_table = self.bq_wrapper.get_table(
project_id=table_reference.projectId,
dataset_id=table_reference.datasetId,
table_id=table_reference.tableId)
except Exception as e:
Comment on lines +722 to +726
load_call = dofn.bq_wrapper.perform_load_job.call_args.kwargs
self.assertEqual(
load_call['additional_load_parameters']['timePartitioning'],
destination_table.timePartitioning)
dofn.bq_wrapper.get_table.assert_called_once()
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request improves the reliability of BigQuery file load operations by ensuring that partitioning settings are correctly propagated to temporary tables. By intelligently fetching destination table metadata and avoiding redundant lookups, the changes ensure that existing partitioning configurations are respected unless overridden by the user. This fix addresses issues where temporary table creation might otherwise lose critical partitioning constraints.

Highlights

  • Partitioning Preservation: Ensures that BigQuery destination partitioning metadata (time and range) is correctly preserved when writing through temporary tables in FILE_LOADS.
  • Smart Lookup Logic: Optimizes destination table lookups by reusing existing schema resolution logic and only injecting partitioning parameters when they are not explicitly provided by the caller.
  • Regression Testing: Added comprehensive test cases to verify inheritance of time/range partitioning and to ensure explicit partitioning parameters take precedence.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request ensures that temporary table loads inherit time and range partitioning parameters from the destination BigQuery table. It introduces helper functions to check and apply these parameters, updates the TriggerLoadJobs process to fetch the destination table configuration, and adds corresponding unit tests. However, the current implementation introduces a performance regression by bypassing the schema cache and making redundant, synchronous get_table API calls on every bundle. It is recommended to cache the fetched destination table locally to avoid unnecessary API calls.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +765 to +785
destination_table = None
hashed_dest = bigquery_tools.get_hashable_destination(table_reference)
should_lookup_destination_table = (
schema is None or
not _has_partitioning_load_parameters(additional_parameters))
if should_lookup_destination_table:
try:
destination_table = self.bq_wrapper.get_table(
project_id=table_reference.projectId,
dataset_id=table_reference.datasetId,
table_id=table_reference.tableId)
except Exception as e:
if schema is None:
_LOGGER.warning(
"Input schema is absent and could not fetch the final "
"destination table's schema [%s]. Creating temp table [%s] "
"will likely fail: %s",
hashed_dest,
job_name,
e)
destination_table = None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This implementation introduces a significant performance regression and redundant BigQuery API calls:

  1. Bypassing the Schema Cache: Since should_lookup_destination_table is evaluated before checking self.schema_cache, if schema is None initially, should_lookup_destination_table will always be True. This forces a synchronous get_table API call on every single bundle/partition even if the schema is already cached in self.schema_cache.
  2. Redundant Calls per Bundle: If schema is provided but partitioning parameters are not (the default case), get_table is called on every single bundle/partition without any caching.

We can resolve both issues by caching the fetched destination_table in a local cache (e.g., self._destination_table_cache) and only calling get_table if we actually need the schema (and it's not in self.schema_cache) or if we need the partitioning parameters.

      hashed_dest = bigquery_tools.get_hashable_destination(table_reference)
      if not hasattr(self, '_destination_table_cache'):
        self._destination_table_cache = {}
      destination_table = self._destination_table_cache.get(hashed_dest)
      if destination_table is None:
        need_schema = schema is None and hashed_dest not in self.schema_cache
        need_partitioning = not _has_partitioning_load_parameters(additional_parameters)
        if need_schema or need_partitioning:
          try:
            destination_table = self.bq_wrapper.get_table(
                project_id=table_reference.projectId,
                dataset_id=table_reference.datasetId,
                table_id=table_reference.tableId)
            self._destination_table_cache[hashed_dest] = destination_table
          except Exception as e:
            if schema is None and hashed_dest not in self.schema_cache:
              _LOGGER.warning(
                  "Input schema is absent and could not fetch the final "
                  "destination table's schema [%s]. Creating temp table [%s] "
                  "will likely fail: %s",
                  hashed_dest,
                  job_name,
                  e)
            destination_table = None

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 7, 2026

Checks are failing. Will not request review until checks are succeeding. If you'd like to override that behavior, comment assign set of reviewers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: 'Failed to copy Non partitioned table to Column partitioned table: not supported.'

2 participants