Skip to content

Commit e9c5c6c

Browse files
Add support for WRITE_TRUNCATE_DATA in BQ Sink
1 parent 12027fb commit e9c5c6c

10 files changed

Lines changed: 142 additions & 17 deletions

File tree

docs/BigQueryMultiTable-batchsink.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ If the bucket was created automatically, it will be deleted after the run finish
5959
**Truncate Table:** Whether or not to truncate the table before writing to it.
6060
Should only be used with the Insert operation.
6161

62+
**Write Disposition**: Describes whether a job should truncate table but preserve metadata or not.
63+
For more details, see [here](https://docs.cloud.google.com/bigquery/docs/reference/auditlogs/rest/Shared.Types/BigQueryAuditMetadata.WriteDisposition).
64+
6265
**Location:** The location where the big query datasets will get created. This value is ignored
6366
if the dataset or temporary bucket already exist.
6467

docs/BigQueryTable-batchsink.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ will be dropped.
7676
**Truncate Table**: Whether or not to truncate the table before writing to it.
7777
Should only be used with the Insert operation.
7878

79+
**Write Disposition**: Describes whether a job should truncate table but preserve metadata or not.
80+
For more details, see [here](https://docs.cloud.google.com/bigquery/docs/reference/auditlogs/rest/Shared.Types/BigQueryAuditMetadata.WriteDisposition).
81+
7982
**Table Key**: List of fields that determines relation between tables during Update and Upsert operations.
8083

8184
**Dedupe By**: Column names and sort order used to choose which input record to update/upsert when there are

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
<groupId>io.cdap.plugin</groupId>
2222
<artifactId>google-cloud</artifactId>
23-
<version>0.23.5</version>
23+
<version>0.23.6</version>
2424
<name>Google Cloud Plugins</name>
2525
<packaging>jar</packaging>
2626
<description>Plugins for Google Big Query</description>

src/main/java/io/cdap/plugin/gcp/bigquery/sink/AbstractBigQuerySink.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ private Configuration getBaseConfiguration(@Nullable CryptoKeyName cmekKeyName)
250250
baseConfiguration.setBoolean(BigQueryConstants.CONFIG_ALLOW_SCHEMA_RELAXATION,
251251
config.isAllowSchemaRelaxation());
252252
baseConfiguration.setStrings(BigQueryConfiguration.OUTPUT_TABLE_WRITE_DISPOSITION.getKey(),
253-
config.getWriteDisposition().name());
253+
config.getWriteDisposition());
254254
baseConfiguration.setStrings(BigQueryConstants.CONFIG_JSON_STRING_FIELDS, config.getJsonStringFields());
255255
// this setting is needed because gcs has default chunk size of 64MB. This is large default chunk size which can
256256
// cause OOM issue if there are many tables being written. See this - CDAP-16670

src/main/java/io/cdap/plugin/gcp/bigquery/sink/AbstractBigQuerySinkConfig.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public abstract class AbstractBigQuerySinkConfig extends BigQueryBaseConfig {
4848
Schema.Type.BOOLEAN, Schema.Type.BYTES, Schema.Type.ARRAY, Schema.Type.RECORD);
4949

5050
public static final String NAME_TRUNCATE_TABLE = "truncateTable";
51+
public static final String NAME_WRITE_DISPOSITION = "writeDisposition";
5152
public static final String NAME_LOCATION = "location";
5253
private static final String NAME_GCS_CHUNK_SIZE = "gcsChunkSize";
5354
public static final String NAME_BQ_JOB_LABELS = "jobLabels";
@@ -77,9 +78,19 @@ public abstract class AbstractBigQuerySinkConfig extends BigQueryBaseConfig {
7778
@Macro
7879
@Nullable
7980
@Description("Whether or not to truncate the table before writing to it. "
80-
+ "Should only be used with the Insert operation. This could overwrite the table schema")
81+
+ "Should only be used with the Insert operation. This could overwrite the table schema based "
82+
+ "on write disposition value.")
8183
protected Boolean truncateTable;
8284

85+
@Name(NAME_WRITE_DISPOSITION)
86+
@Macro
87+
@Nullable
88+
@Description("WRITE_TRUNCATE_DATA preserves the table metadata where as WRITE_TRUNCATE does not. "
89+
+ "For more details, see "
90+
+ "https://docs.cloud.google.com/bigquery/docs/reference/auditlogs/rest/Shared.Types/"
91+
+ "BigQueryAuditMetadata.WriteDisposition.")
92+
protected String writeDisposition;
93+
8394
@Name(NAME_LOCATION)
8495
@Macro
8596
@Nullable
@@ -143,9 +154,16 @@ public boolean isAllowSchemaRelaxation() {
143154
return allowSchemaRelaxation == null ? false : allowSchemaRelaxation;
144155
}
145156

146-
public JobInfo.WriteDisposition getWriteDisposition() {
147-
return isTruncateTableSet() ? JobInfo.WriteDisposition.WRITE_TRUNCATE
148-
: JobInfo.WriteDisposition.WRITE_APPEND;
157+
private String getTruncateTableWriteDisposition() {
158+
if (writeDisposition == null) {
159+
return JobInfo.WriteDisposition.WRITE_TRUNCATE.name();
160+
}
161+
return writeDisposition;
162+
}
163+
164+
public String getWriteDisposition() {
165+
return isTruncateTableSet() ? getTruncateTableWriteDisposition()
166+
: JobInfo.WriteDisposition.WRITE_APPEND.name();
149167
}
150168

151169
public boolean isTruncateTableSet() {

src/main/java/io/cdap/plugin/gcp/bigquery/sink/BigQueryOutputFormat.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ private void importFromGcs(String projectId, TableReference tableRef, @Nullable
351351

352352
Map<String, String> fieldDescriptions = new HashMap<>();
353353
if (JobInfo.WriteDisposition.WRITE_TRUNCATE
354-
.equals(JobInfo.WriteDisposition.valueOf(writeDisposition)) && tableExists) {
354+
.equals(writeDisposition.toUpperCase()) && tableExists) {
355355
List<TableFieldSchema> tableFieldSchemas = Optional.ofNullable(bigQueryHelper.getTable(tableRef))
356356
.map(it -> it.getSchema())
357357
.map(it -> it.getFields())
@@ -399,8 +399,8 @@ private void importFromGcs(String projectId, TableReference tableRef, @Nullable
399399
// Schema update options should only be specified with WRITE_APPEND disposition,
400400
// or with WRITE_TRUNCATE disposition on a table partition - The logic below should change when we support
401401
// insertion into single partition
402-
if (allowSchemaRelaxation && !JobInfo.WriteDisposition.WRITE_TRUNCATE
403-
.equals(JobInfo.WriteDisposition.valueOf(writeDisposition))) {
402+
if (allowSchemaRelaxation && !JobInfo.WriteDisposition.WRITE_TRUNCATE.name()
403+
.equals(writeDisposition.toUpperCase())) {
404404
loadConfig.setSchemaUpdateOptions(Arrays.asList(
405405
JobInfo.SchemaUpdateOption.ALLOW_FIELD_ADDITION.name(),
406406
JobInfo.SchemaUpdateOption.ALLOW_FIELD_RELAXATION.name()));
@@ -428,7 +428,7 @@ private void importFromGcs(String projectId, TableReference tableRef, @Nullable
428428
if (operation.equals(Operation.INSERT) && gcsPaths.size() <= BQ_IMPORT_MAX_BATCH_SIZE) {
429429
// Directly load data into destination table when total no of input paths is loadable into BQ
430430
loadConfig.setSourceUris(gcsPaths);
431-
loadConfig.setWriteDisposition(writeDisposition);
431+
loadConfig.setWriteDisposition(writeDisposition.toUpperCase());
432432
loadConfig.setDestinationTable(tableRef);
433433

434434
JobConfiguration config = new JobConfiguration();
@@ -487,7 +487,7 @@ private void loadInBatchesInTempTable(TableReference tableRef, JobConfigurationL
487487
.setTableId(temporaryTableName);
488488

489489
loadConfig.setDestinationTable(temporaryTableReference);
490-
loadConfig.setWriteDisposition(JobInfo.WriteDisposition.WRITE_APPEND.toString());
490+
loadConfig.setWriteDisposition(JobInfo.WriteDisposition.WRITE_APPEND.name());
491491

492492
// Split the list of files in batches 10000 (current bq load job limit) and import /append onto a temp table
493493
List<List<String>> gcsPathsInBatches = Lists.partition(gcsPaths, BQ_IMPORT_MAX_BATCH_SIZE);
@@ -723,7 +723,7 @@ private static TableSchema createTableSchemaFromFields(String fieldsJson) throws
723723
private void updateFieldDescriptions(String writeDisposition, TableReference tableRef,
724724
Map<String, String> fieldDescriptions) throws IOException {
725725
if (JobInfo.WriteDisposition.WRITE_TRUNCATE
726-
.equals(JobInfo.WriteDisposition.valueOf(writeDisposition))) {
726+
.equals(writeDisposition.toUpperCase())) {
727727

728728
Table table = bigQueryHelper.getTable(tableRef);
729729
List<TableFieldSchema> tableFieldSchemas = Optional.ofNullable(table)

src/main/java/io/cdap/plugin/gcp/bigquery/sink/BigQuerySinkConfig.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,15 +198,19 @@ public BigQuerySinkConfig(@Nullable String referenceName, String dataset, String
198198

199199
private BigQuerySinkConfig(@Nullable String referenceName, @Nullable String project,
200200
@Nullable String serviceAccountType, @Nullable String serviceFilePath,
201-
@Nullable String serviceAccountJson,
202-
@Nullable String dataset, @Nullable String table, @Nullable String location,
203-
@Nullable String cmekKey, @Nullable String bucket, @Nullable String jobLabelKeyValue) {
201+
@Nullable String serviceAccountJson, @Nullable String dataset,
202+
@Nullable String table, @Nullable String location,
203+
@Nullable String cmekKey, @Nullable String bucket,
204+
@Nullable String jobLabelKeyValue, @Nullable String writeDisposition,
205+
@Nullable Boolean truncateTable) {
204206
super(new BigQueryConnectorConfig(project, project, serviceAccountType,
205207
serviceFilePath, serviceAccountJson), dataset, cmekKey, bucket);
206208
this.referenceName = referenceName;
207209
this.table = table;
208210
this.location = location;
209211
this.jobLabelKeyValue = jobLabelKeyValue;
212+
this.writeDisposition = writeDisposition;
213+
this.truncateTable = truncateTable;
210214
}
211215

212216
public String getTable() {
@@ -715,6 +719,8 @@ public static class Builder {
715719
private String location;
716720
private String bucket;
717721
private String jobLabelKeyValue;
722+
private String writeDisposition;
723+
private Boolean truncateTable;
718724

719725
public BigQuerySinkConfig.Builder setReferenceName(@Nullable String referenceName) {
720726
this.referenceName = referenceName;
@@ -770,6 +776,16 @@ public BigQuerySinkConfig.Builder setJobLabelKeyValue(@Nullable String jobLabelK
770776
return this;
771777
}
772778

779+
public BigQuerySinkConfig.Builder setWriteDisposition(@Nullable String writeDisposition) {
780+
this.writeDisposition = writeDisposition;
781+
return this;
782+
}
783+
784+
public BigQuerySinkConfig.Builder setTruncateTable(@Nullable Boolean truncateTable) {
785+
this.truncateTable = truncateTable;
786+
return this;
787+
}
788+
773789
public BigQuerySinkConfig build() {
774790
return new BigQuerySinkConfig(
775791
referenceName,
@@ -782,9 +798,10 @@ public BigQuerySinkConfig build() {
782798
location,
783799
cmekKey,
784800
bucket,
785-
jobLabelKeyValue
801+
jobLabelKeyValue,
802+
writeDisposition,
803+
truncateTable
786804
);
787805
}
788-
789806
}
790807
}

src/test/java/io/cdap/plugin/gcp/bigquery/sink/BigQuerySinkConfigTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.cdap.plugin.gcp.bigquery.sink;
1818

19+
import com.google.cloud.bigquery.JobInfo;
1920
import com.google.cloud.bigquery.TimePartitioning;
2021
import io.cdap.cdap.api.data.schema.Schema;
2122
import io.cdap.cdap.etl.api.FailureCollector;
@@ -56,6 +57,27 @@ public void setup() throws NoSuchMethodException {
5657
arguments = new HashMap<>();
5758
}
5859

60+
@Test
61+
public void testValidateWriteDisposition() {
62+
BigQuerySinkConfig bigQuerySinkConfig =
63+
BigQuerySinkConfig.builder()
64+
.setTruncateTable(true)
65+
.setWriteDisposition("WRITE_TRUNCATE_DATA")
66+
.build();
67+
Assert.assertEquals("WRITE_TRUNCATE_DATA", bigQuerySinkConfig.getWriteDisposition());
68+
69+
bigQuerySinkConfig = BigQuerySinkConfig.builder().setWriteDisposition("WRITE_APPEND").build();
70+
Assert.assertEquals(bigQuerySinkConfig.getWriteDisposition(),
71+
JobInfo.WriteDisposition.WRITE_APPEND.name());
72+
73+
bigQuerySinkConfig = BigQuerySinkConfig.builder()
74+
.setTruncateTable(true)
75+
.setWriteDisposition("WRITE_TRUNCATE")
76+
.build();
77+
Assert.assertEquals(bigQuerySinkConfig.getWriteDisposition(),
78+
JobInfo.WriteDisposition.WRITE_TRUNCATE.name());
79+
}
80+
5981
@Test
6082
public void testValidateTimePartitioningColumnWithHourAndDate() throws
6183
InvocationTargetException, IllegalAccessException {

widgets/BigQueryMultiTable-batchsink.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,25 @@
124124
"label": "False"
125125
}
126126
}
127+
},
128+
{
129+
"name": "writeDisposition",
130+
"widget-type": "radio-group",
131+
"label": "Write Disposition",
132+
"widget-attributes": {
133+
"layout": "inline",
134+
"default": "WRITE_TRUNCATE",
135+
"options": [
136+
{
137+
"id": "WRITE_TRUNCATE",
138+
"label": "WRITE_TRUNCATE"
139+
},
140+
{
141+
"id": "WRITE_TRUNCATE_DATA",
142+
"label": "WRITE_TRUNCATE_DATA"
143+
}
144+
]
145+
}
127146
}
128147
]
129148
},
@@ -287,6 +306,18 @@
287306
"name": "connection"
288307
}
289308
]
309+
},
310+
{
311+
"name": "showWriteDisposition",
312+
"condition": {
313+
"expression": "truncateTable == true"
314+
},
315+
"show": [
316+
{
317+
"type": "property",
318+
"name": "writeDisposition"
319+
}
320+
]
290321
}
291322
],
292323
"jump-config": {

widgets/BigQueryTable-batchsink.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,25 @@
221221
}
222222
}
223223
},
224+
{
225+
"name": "writeDisposition",
226+
"widget-type": "radio-group",
227+
"label": "Write Disposition",
228+
"widget-attributes": {
229+
"layout": "inline",
230+
"default": "WRITE_TRUNCATE",
231+
"options": [
232+
{
233+
"id": "WRITE_TRUNCATE",
234+
"label": "WRITE_TRUNCATE"
235+
},
236+
{
237+
"id": "WRITE_TRUNCATE_DATA",
238+
"label": "WRITE_TRUNCATE_DATA"
239+
}
240+
]
241+
}
242+
},
224243
{
225244
"name": "allowSchemaRelaxation",
226245
"widget-type": "toggle",
@@ -542,6 +561,18 @@
542561
"name": "relationTableKey"
543562
}
544563
]
564+
},
565+
{
566+
"name": "showWriteDisposition",
567+
"condition": {
568+
"expression": "truncateTable == true"
569+
},
570+
"show": [
571+
{
572+
"type": "property",
573+
"name": "writeDisposition"
574+
}
575+
]
545576
}
546577
],
547578
"jump-config": {

0 commit comments

Comments
 (0)