Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 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
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ class UnivocityGenerator(
private val writerSettings = options.asWriterSettings
writerSettings.setHeaders(schema.fieldNames: _*)
private val gen = new CsvWriter(writer, writerSettings)
private var printHeader = options.headerFlag

// A `ValueConverter` is responsible for converting a value of an `InternalRow` to `String`.
// When the value is null, this converter should not be called.
Expand Down Expand Up @@ -72,15 +71,15 @@ class UnivocityGenerator(
values
}

def writeHeaders(): Unit = {
gen.writeHeaders()
}

/**
* Writes a single InternalRow to CSV using Univocity.
*/
def write(row: InternalRow): Unit = {
if (printHeader) {
gen.writeHeaders()
}
gen.writeRow(convertRow(row): _*)
printHeader = false
}

def writeToString(row: InternalRow): String = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ class SingleDirectoryDataWriter(
path = currentPath,
dataSchema = description.dataColumns.toStructType,
context = taskAttemptContext)
currentWriter.init()

statsTrackers.foreach(_.newFile(currentPath))
}
Expand Down Expand Up @@ -237,6 +238,7 @@ class DynamicPartitionDataWriter(
path = currentPath,
dataSchema = description.dataColumns.toStructType,
context = taskAttemptContext)
currentWriter.init()

statsTrackers.foreach(_.newFile(currentPath))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ abstract class OutputWriterFactory extends Serializable {
* executor side. This instance is used to persist rows to this single output file.
*/
abstract class OutputWriter {
/** Initializes before writing any rows. Invoked on executor size. */
def init(): Unit = {}
Comment thread
koertkuipers marked this conversation as resolved.
Outdated

/**
* Persists a single row. Invoked on the executor side. When writing to dynamically partitioned
* tables, dynamic partition columns are not included in rows to be written.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,23 @@ private[csv] class CsvOutputWriter(

private var univocityGenerator: Option[UnivocityGenerator] = None

override def write(row: InternalRow): Unit = {
val gen = univocityGenerator.getOrElse {
val charset = Charset.forName(params.charset)
val os = CodecStreams.createOutputStreamWriter(context, new Path(path), charset)
val newGen = new UnivocityGenerator(dataSchema, os, params)
univocityGenerator = Some(newGen)
newGen
private def getOrCreateGen(): UnivocityGenerator = univocityGenerator.getOrElse {
Comment thread
koertkuipers marked this conversation as resolved.
Outdated
val charset = Charset.forName(params.charset)
val os = CodecStreams.createOutputStreamWriter(context, new Path(path), charset)
val newGen = new UnivocityGenerator(dataSchema, os, params)
univocityGenerator = Some(newGen)
newGen
}

override def init(): Unit = {
if (params.headerFlag) {
val gen = getOrCreateGen()
gen.writeHeaders()
}
}

override def write(row: InternalRow): Unit = {
val gen = getOrCreateGen()
gen.write(row)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1987,6 +1987,18 @@ class CSVSuite extends QueryTest with SharedSQLContext with SQLTestUtils with Te
assert(errMsg2.contains("'lineSep' can contain only 1 character"))
}

test("SPARK-26208: write and read empty data to csv file with header") {
withTempPath { path =>
val df1 = Seq.empty[(String, String)].toDF("x", "y")
Comment thread
koertkuipers marked this conversation as resolved.
Outdated
df1.printSchema
Comment thread
koertkuipers marked this conversation as resolved.
Outdated
df1.write.format("csv").option("header", true).save(path.getAbsolutePath)
val df2 = spark.read.format("csv").option("header", true).load(path.getAbsolutePath)
Comment thread
koertkuipers marked this conversation as resolved.
Outdated
df2.printSchema
Comment thread
koertkuipers marked this conversation as resolved.
Outdated
assert(df1.schema === df2.schema)
checkAnswer(df1, df2)
}
}

test("do not produce empty files for empty partitions") {
withTempPath { dir =>
val path = dir.getCanonicalPath
Expand Down