This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new 3bad898630 add header row when creating text file, fixes #7316 (#7317)
3bad898630 is described below
commit 3bad8986300d140105a24ab9dac47839f79ceeca
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Fri Jun 19 11:53:35 2026 +0200
add header row when creating text file, fixes #7316 (#7317)
---
.../transforms/textfileoutput/TextFileOutput.java | 19 ++++++
.../textfileoutput/TextFileOutputTest.java | 76 ++++++++++++++++++++++
2 files changed, 95 insertions(+)
diff --git
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutput.java
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutput.java
index bbd440d785..82cb6587c0 100644
---
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutput.java
+++
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutput.java
@@ -404,6 +404,25 @@ public class TextFileOutput extends
BaseTransform<TextFileOutputMeta, TextFileOu
if (data.writer != null) {
if (data.outputRowMeta != null && meta.isFooterEnabled()) {
writeHeader();
+ } else if (first && meta.isHeaderEnabled()) {
+ // The file was created at pipeline start (init() -> initOutput())
but no rows were
+ // received, so the header was never written. Honor the enabled
header for the empty
+ // result set.
+ String filename = getOutputFileName(null);
+ if (isWriteHeader(filename)) {
+ if (data.outputRowMeta == null) {
+ data.outputRowMeta =
+ getPipelineMeta().getPrevTransformFields(variables,
getTransformMeta());
+ if (data.outputRowMeta != null) {
+ meta.getFields(
+ data.outputRowMeta, getTransformName(), null, null, this,
metadataProvider);
+ }
+ }
+ // Without column metadata there is nothing to write a header from.
+ if (data.outputRowMeta != null) {
+ writeHeader();
+ }
+ }
}
} else if (!Utils.isEmpty(resolve(meta.getEndedLine())) &&
!meta.isFileNameInField()) {
String filename = getOutputFileName(null);
diff --git
a/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputTest.java
b/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputTest.java
index 2e897f5548..9cdb2cbac5 100644
---
a/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputTest.java
+++
b/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputTest.java
@@ -753,6 +753,82 @@ class TextFileOutputTest {
assertEquals(1, textFileOutputSpy.getResultFiles().size());
}
+ /**
+ * When the file is created at pipeline start ('Do not create file at start'
unchecked) and the
+ * header is enabled, the header must still be written even when no rows are
received. Without the
+ * fix this produced an empty file with no header. With the transform's
fields configured, the
+ * header columns come from those fields - exactly as they would for a
regular row.
+ */
+ @Test
+ void testHeaderWrittenForEmptyFileCreatedAtStart_configuredFields() throws
Exception {
+ assertEquals("Name Surname\n",
writeHeaderOnlyFileWithoutRows(textFileFields));
+ }
+
+ /**
+ * Same as above but with no fields configured on the transform: the header
columns are taken from
+ * the incoming stream metadata (resolved from the previous transform when
no rows flow), again
+ * mirroring the regular-row code path.
+ */
+ @Test
+ void testHeaderWrittenForEmptyFileCreatedAtStart_allStreamFields() throws
Exception {
+ assertEquals("Name Surname\n", writeHeaderOnlyFileWithoutRows(new
ArrayList<>()));
+ }
+
+ /**
+ * Runs a TextFileOutput with the header enabled, the file created at
pipeline start and zero
+ * rows, then returns the resulting file content. The incoming stream always
carries the
+ * Name/Surname fields; {@code outputFields} controls whether those columns
are explicitly
+ * configured on the transform or left empty (write all stream fields).
+ */
+ private String writeHeaderOnlyFileWithoutRows(List<TextFileField>
outputFields) throws Exception {
+ FileObject file = createTemplateFile(null); // file does not exist yet
+ String pathToFile = file.getName().getURI();
+
+ TextFileOutputData textFileOutputData = new TextFileOutputData();
+ TextFileOutputTestHandler handler =
+ new TextFileOutputTestHandler(
+ transformMockHelper.transformMeta,
+ transformMockHelper.iTransformMeta,
+ textFileOutputData,
+ 0,
+ transformMockHelper.pipelineMeta,
+ transformMockHelper.pipeline);
+
+ TextFileOutputMeta.FileSettings fileSettings =
+ Mockito.mock(TextFileOutputMeta.FileSettings.class);
+ Mockito.when(fileSettings.isDoNotOpenNewFileInit()).thenReturn(false); //
create file at start
+ Mockito.when(fileSettings.isFileAppended()).thenReturn(false);
+ Mockito.when(fileSettings.getFileName()).thenReturn(pathToFile);
+
Mockito.when(transformMockHelper.iTransformMeta.getFileSettings()).thenReturn(fileSettings);
+
Mockito.when(transformMockHelper.iTransformMeta.isHeaderEnabled()).thenReturn(true);
+
Mockito.when(transformMockHelper.iTransformMeta.getEndedLine()).thenReturn(null);
+
Mockito.when(transformMockHelper.iTransformMeta.getOutputFields()).thenReturn(outputFields);
+
+ // With zero rows the output row metadata is resolved from the previous
transform's fields,
+ // just like getInputRowMeta() would supply it once the first row arrives.
+ IRowMeta prevFields = Mockito.mock(IRowMeta.class);
+ for (int i = 0; i < textFileFields.size(); i++) {
+ String name = textFileFields.get(i).getName();
+ Mockito.when(prevFields.searchValueMeta(name)).thenReturn(new
ValueMetaString(name));
+ Mockito.when(prevFields.getValueMeta(i)).thenReturn(new
ValueMetaString(name));
+ Mockito.when(prevFields.indexOfValue(name)).thenReturn(i);
+ }
+ Mockito.when(prevFields.size()).thenReturn(textFileFields.size());
+ Mockito.when(
+ transformMockHelper.pipelineMeta.getPrevTransformFields(
+ Mockito.any(IVariables.class),
Mockito.any(TransformMeta.class)))
+ .thenReturn(prevFields);
+
+ handler.init();
+ handler.setRow(null); // no rows received
+ handler.processRow();
+ handler.dispose();
+
+ assertTrue(handler.errors.isEmpty(), handler.errors.toString());
+ assertTrue(file.exists());
+ return IOUtils.toString(file.getContent().getInputStream(),
StandardCharsets.UTF_8);
+ }
+
@Test
void testFastDumpDisableStreamEncodeTest() throws Exception {