Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@

* Support for X source added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)).
* (Java) FirestoreIO: added `RpcQosOptions.Builder.withRampupThrottlingDisabled()`, mirroring the existing DatastoreIO option, to opt out of the 500/50/5 write ramp-up ([#27170](https://github.com/apache/beam/issues/27170)).

* (Python) The BigQuery IO writer now allows the `WRITE_TRUNCATE_DATA` write_disposition ([#40622](https://github.com/apache/beam/pull/40622)).

## New Features / Improvements

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3038,6 +3038,17 @@
*/
WRITE_TRUNCATE,

/**
* Specifies that write should replace a table, but not the table schema.
*
* <p>The replacement may occur in multiple steps - for instance by first removing the
* existing table, then creating a replacement, then filling it in. This is not an atomic
* operation, and external programs may see the table in any of these intermediate steps.
*
* <p>Note: This write disposition is only supported for the FILE_LOADS write method.
*/
WRITE_TRUNCATE_DATA,

/** Specifies that rows may be appended to an existing table. */
WRITE_APPEND,

Expand Down Expand Up @@ -3973,8 +3984,9 @@
getBigLakeConfiguration() == null,
"bigLakeConfiguration is only supported when using STORAGE_WRITE_API or STORAGE_API_AT_LEAST_ONCE.");
} else {
if (getWriteDisposition() == WriteDisposition.WRITE_TRUNCATE) {
LOG.error("The Storage API sink does not support the WRITE_TRUNCATE write disposition.");
WriteDisposition disposition = getWriteDisposition()

Check failure on line 3987 in sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIO.java

View workflow job for this annotation

GitHub Actions / beam_PreCommit_Java_Examples_Dataflow

';' expected
if (disposition == WriteDisposition.WRITE_TRUNCATE || disposition == WriteDisposition.WRITE_TRUNCATE_DATA) {
LOG.error("The Storage API sink does not support the "+disposition.toString()+" write disposition.");
}
Map<String, String> bigLakeConfiguration = getBigLakeConfiguration();
if (bigLakeConfiguration != null) {
Expand Down Expand Up @@ -4229,6 +4241,9 @@
checkArgument(
getWriteDisposition() != WriteDisposition.WRITE_TRUNCATE,
"WriteDisposition.WRITE_TRUNCATE is not supported for an unbounded PCollection.");
checkArgument(
getWriteDisposition() != WriteDisposition.WRITE_TRUNCATE_DATA,
"WriteDisposition.WRITE_TRUNCATE_DATA is not supported for an unbounded PCollection.");
InsertRetryPolicy retryPolicy =
MoreObjects.firstNonNull(getFailedInsertRetryPolicy(), InsertRetryPolicy.alwaysRetry());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,12 @@
if (!loadConfig
.getWriteDisposition()
.equals(BigQueryIO.Write.WriteDisposition.WRITE_TRUNCATE.toString())
&& !loadConfig
.getWriteDisposition()
.equals(BigQueryIO.Write.WriteDisposition.WRITE_TRUNCATE_DATA.toString()
&& !loadConfig

Check failure on line 263 in sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/UpdateSchemaDestination.java

View workflow job for this annotation

GitHub Actions / beam_PreCommit_Java_Examples_Dataflow

bad operand types for binary operator '&&'
.getWriteDisposition()
.equals(BigQueryIO.Write.WriteDisposition.WRITE_APPEND.toString())) {

Check failure on line 265 in sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/UpdateSchemaDestination.java

View workflow job for this annotation

GitHub Actions / beam_PreCommit_Java_Examples_Dataflow

')' expected
return null;
}
final Table destinationTable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ public static Builder builder() {
@SchemaFieldDescription(
"Specifies the action that occurs if the destination table already exists. "
+ "The following values are supported: "
+ "WRITE_TRUNCATE (overwrites the table data), "
+ "WRITE_TRUNCATE (overwrites the table data and schema), "
+ "WRITE_TRUNCATE_DATA (overwrites the table data), "
+ "WRITE_APPEND (append the data to the table), "
+ "WRITE_EMPTY (job must fail if the table is not empty).")
@Pure
Expand Down
5 changes: 5 additions & 0 deletions sdks/python/apache_beam/io/gcp/bigquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,7 @@ class BigQueryDisposition(object):

CREATE_NEVER = 'CREATE_NEVER'
CREATE_IF_NEEDED = 'CREATE_IF_NEEDED'
WRITE_TRUNCATE_DATA = 'WRITE_TRUNCATE_DATA'
WRITE_TRUNCATE = 'WRITE_TRUNCATE'
WRITE_APPEND = 'WRITE_APPEND'
WRITE_EMPTY = 'WRITE_EMPTY'
Expand All @@ -606,6 +607,7 @@ def validate_create(disposition):
@staticmethod
def validate_write(disposition):
values = (
BigQueryDisposition.WRITE_TRUNCATE_DATA,

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.

🔴 Critical: Allowing WRITE_TRUNCATE_DATA here enables it for BigQueryBatchFileLoads, but sdks/python/apache_beam/io/gcp/bigquery_file_loads.py only checks for ('WRITE_EMPTY', 'WRITE_TRUNCATE') in BigQueryBatchFileLoads._load_data (line 970) and ('WRITE_TRUNCATE', 'WRITE_APPEND') in UpdateDestinationSchema.process (line 368).

Without including 'WRITE_TRUNCATE_DATA' in BigQueryBatchFileLoads._load_data, loads that spill into multiple temporary tables will not be grouped by destination before TriggerCopyJobs. As a result, parallel copy jobs will each execute with WRITE_TRUNCATE_DATA and overwrite one another's data. Please update both checks in bigquery_file_loads.py.

BigQueryDisposition.WRITE_TRUNCATE,
BigQueryDisposition.WRITE_APPEND,
BigQueryDisposition.WRITE_EMPTY)
Expand Down Expand Up @@ -1517,6 +1519,7 @@ def __init__(
- BigQueryDisposition.CREATE_NEVER: fail the write if does not exist.
write_disposition: A string describing what happens if the table has
already some data. Possible values are:
- BigQueryDisposition.WRITE_TRUNCATE_DATA: delete rows, keep schema.
- BigQueryDisposition.WRITE_TRUNCATE: delete existing rows.
- BigQueryDisposition.WRITE_APPEND: add to existing rows.
- BigQueryDisposition.WRITE_EMPTY: fail the write if table not empty.
Expand Down Expand Up @@ -1558,6 +1561,7 @@ def __init__(
self.create_disposition = create_disposition
self.write_disposition = write_disposition
if write_disposition in (BigQueryDisposition.WRITE_EMPTY,
BigQueryDisposition.WRITE_TRUNCATE_DATA,
BigQueryDisposition.WRITE_TRUNCATE):
raise ValueError(
'Write disposition %s is not supported for'
Expand Down Expand Up @@ -2126,6 +2130,7 @@ def __init__(
write_disposition (BigQueryDisposition): A string describing what happens
if the table has already some data. Possible values are:

* :attr:`BigQueryDisposition.WRITE_TRUNCATE_DATA`: delete table data but preserve table metadata.
* :attr:`BigQueryDisposition.WRITE_TRUNCATE`: delete existing rows.
* :attr:`BigQueryDisposition.WRITE_APPEND`: add to existing rows.
* :attr:`BigQueryDisposition.WRITE_EMPTY`: fail the write if table not
Expand Down
21 changes: 14 additions & 7 deletions sdks/python/apache_beam/io/gcp/bigquery_file_loads.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,11 @@ def process(self, element, schema_mod_job_name_prefix):
# When writing to normal tables WRITE_TRUNCATE will overwrite the schema but
# when writing to a partition, care needs to be taken to update the schema
# even on WRITE_TRUNCATE.
if (self._write_disposition not in ('WRITE_TRUNCATE', 'WRITE_APPEND') or
if (self._write_disposition not in (
'WRITE_TRUNCATE',
'WRITE_TRUNCATE_DATA',
'WRITE_APPEND'
) or
not additional_parameters or
not additional_parameters.get("schemaUpdateOptions")):
# No need to modify schema of destination table
Expand Down Expand Up @@ -613,11 +617,12 @@ def _determine_write_disposition(self, copy_to_reference) -> tuple[bool, str]:
Determines the write disposition for a BigQuery copy job,
based on destination.

When the write_disposition for a job is WRITE_TRUNCATE, multiple copy jobs
to the same destination can interfere with each other, truncate data, and
write to the BigQuery table repeatedly. To prevent this, the first copy job
runs with the user's specified write_disposition, but subsequent jobs must
always use WRITE_APPEND. This ensures that subsequent copy jobs do not
When the write_disposition for a job is WRITE_TRUNCATE or
WRITE_TRUNCATE_DATA, multiple copy jobs to the same destination can
interfere with each other, truncate data, and write to the BigQuery
table repeatedly. To prevent this, the first copy job runs with the
user's specified write_disposition, but subsequent jobs must always
use WRITE_APPEND. This ensures that subsequent copy jobs do not
clear out data appended by previous jobs.

Args:
Expand Down Expand Up @@ -1231,7 +1236,9 @@ def _load_data(
load_job_project_id=self.load_job_project_id),
schema_mod_job_name_pcv))

if self.write_disposition in ('WRITE_EMPTY', 'WRITE_TRUNCATE'):
if self.write_disposition in (
'WRITE_EMPTY', 'WRITE_TRUNCATE', 'WRITE_TRUNCATE_DATA'
):
# All loads going to the same table must be processed together so that
# the truncation happens only once. See
# https://github.com/apache/beam/issues/24535.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5440,12 +5440,15 @@ class JobConfigurationTableCopy(_messages.Message):
destination table already exists. The following values are supported: *
WRITE_TRUNCATE: If the table already exists, BigQuery overwrites the
table data and uses the schema and table constraints from the source
table. * WRITE_APPEND: If the table already exists, BigQuery appends the
data to the table. * WRITE_EMPTY: If the table already exists and
contains data, a 'duplicate' error is returned in the job result. The
default value is WRITE_EMPTY. Each action is atomic and only occurs if
BigQuery is able to complete the job successfully. Creation, truncation
and append actions occur as one atomic update upon job completion.
table. * WRITE_TRUNCATE_DATA: If the table already exists, BigQuery
overwrites the table data, but does not use schema and table constraints
from the source table.* WRITE_APPEND: If the table already exists,

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.

🟡 Medium: bigquery_v2_messages.py is an autogenerated Apitools client file (# NOTE: This file is autogenerated and should not be edited by hand.), and writeDisposition is an unvalidated _messages.StringField(8), so editing this docstring has no runtime effect. In addition, lines 5445–5450 introduce trailing whitespace that will fail lint checks (table.* WRITE_APPEND: ). Consider reverting manual edits to this file or removing the trailing whitespace.

BigQuery appends the data to the table. * WRITE_EMPTY: If the table
already exists and contains data, a 'duplicate' error is returned in
the job result. The default value is WRITE_EMPTY. Each action is atomic
and only occurs if BigQuery is able to complete the job successfully.
Creation, truncation and append actions occur as one atomic update
upon job completion.
"""

class OperationTypeValueValuesEnum(_messages.Enum):
Expand Down
Loading