Skip to content

Commit bd204bb

Browse files
authored
Optionally set null marker in csv exports in BaseSQLToGCSOperator (#11409)
1 parent 9416bed commit bd204bb

2 files changed

Lines changed: 29 additions & 0 deletions

File tree

airflow/providers/google/cloud/transfers/sql_to_gcs.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class BaseSQLToGCSOperator(BaseOperator):
5757
:type export_format: str
5858
:param field_delimiter: The delimiter to be used for CSV files.
5959
:type field_delimiter: str
60+
:param null_marker: The null marker to be used for CSV files.
61+
:type null_marker: str
6062
:param gzip: Option to compress file for upload (does not apply to schemas).
6163
:type gzip: bool
6264
:param schema: The schema to use, if any. Should be a list of dict or
@@ -109,6 +111,7 @@ def __init__(
109111
approx_max_file_size_bytes=1900000000,
110112
export_format='json',
111113
field_delimiter=',',
114+
null_marker=None,
112115
gzip=False,
113116
schema=None,
114117
parameters=None,
@@ -136,6 +139,7 @@ def __init__(
136139
self.approx_max_file_size_bytes = approx_max_file_size_bytes
137140
self.export_format = export_format.lower()
138141
self.field_delimiter = field_delimiter
142+
self.null_marker = null_marker
139143
self.gzip = gzip
140144
self.schema = schema
141145
self.parameters = parameters
@@ -204,6 +208,8 @@ def _write_local_data_files(self, cursor):
204208
row = self.convert_types(schema, col_type_dict, row)
205209

206210
if self.export_format == 'csv':
211+
if self.null_marker is not None:
212+
row = [value if value is not None else self.null_marker for value in row]
207213
csv_writer.writerow(row)
208214
else:
209215
row_dict = dict(zip(schema, row))

tests/providers/google/cloud/transfers/test_sql_to_gcs.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,26 @@ def test_exec(
159159
mock_flush.assert_called_once()
160160
mock_upload.assert_called_once_with(BUCKET, FILENAME, TMP_FILE_NAME, mime_type=APP_JSON, gzip=False)
161161
mock_close.assert_called_once()
162+
163+
# Test null marker
164+
cursor_mock.__iter__ = Mock(return_value=iter(INPUT_DATA))
165+
mock_convert_type.return_value = None
166+
167+
operator = DummySQLToGCSOperator(
168+
sql=SQL,
169+
bucket=BUCKET,
170+
filename=FILENAME,
171+
task_id=TASK_ID,
172+
export_format="csv",
173+
null_marker="NULL",
174+
)
175+
operator.execute(context=dict())
176+
177+
mock_writerow.assert_has_calls(
178+
[
179+
mock.call(COLUMNS),
180+
mock.call(["NULL", "NULL", "NULL"]),
181+
mock.call(["NULL", "NULL", "NULL"]),
182+
mock.call(["NULL", "NULL", "NULL"]),
183+
]
184+
)

0 commit comments

Comments
 (0)