Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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 @@ -60,6 +60,8 @@ private[avro] class AvroOutputWriter(

}.getRecordWriter(context)

override def init(): Unit = {}

override def write(row: InternalRow): Unit = {
val key = new AvroKey(serializer.serialize(row).asInstanceOf[GenericRecord])
recordWriter.write(key, NullWritable.get())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ private[libsvm] class LibSVMOutputWriter(
// This `asInstanceOf` is safe because it's guaranteed by `LibSVMFileFormat.verifySchema`
private val udt = dataSchema(1).dataType.asInstanceOf[VectorUDT]

override def init(): Unit = {}

override def write(row: InternalRow): Unit = {
val label = row.getDouble(0)
val vector = udt.deserialize(row.getStruct(1, udt.sqlType.length))
Expand Down
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 @@ -175,6 +175,12 @@ private[csv] class CsvOutputWriter(

private val gen = new UnivocityGenerator(dataSchema, writer, params)

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

override def write(row: InternalRow): Unit = gen.write(row)

override def close(): Unit = gen.close()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ private[json] class JsonOutputWriter(
// create the Generator without separator inserted between 2 records
private[this] val gen = new JacksonGenerator(dataSchema, writer, options)

override def init(): Unit = {}

override def write(row: InternalRow): Unit = {
gen.write(row)
gen.writeLineEnding()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ private[orc] class OrcOutputWriter(
recordWriter
}

override def init(): Unit = {}

override def write(row: InternalRow): Unit = {
recordWriter.write(NullWritable.get(), serializer.serialize(row))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ private[parquet] class ParquetOutputWriter(path: String, context: TaskAttemptCon
}.getRecordWriter(context)
}

override def init(): Unit = {}

override def write(row: InternalRow): Unit = recordWriter.write(null, row)

override def close(): Unit = recordWriter.close(context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ class TextOutputWriter(

private val writer = CodecStreams.createOutputStream(context, new Path(path))

override def init(): Unit = {}

override def write(row: InternalRow): Unit = {
if (!row.isNullAt(0)) {
val utf8string = row.getUTF8String(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1986,4 +1986,16 @@ class CSVSuite extends QueryTest with SharedSQLContext with SQLTestUtils with Te
}.getMessage
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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ class HiveOutputWriter(
private val wrappers = fieldOIs.zip(dataTypes).map { case (f, dt) => wrapperFor(f, dt) }
private val outputData = new Array[Any](fieldOIs.length)

override def init(): Unit = {}

override def write(row: InternalRow): Unit = {
var i = 0
while (i < fieldOIs.length) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ private[orc] class OrcOutputWriter(
).asInstanceOf[RecordWriter[NullWritable, Writable]]
}

override def init(): Unit = {}

override def write(row: InternalRow): Unit = {
recordWriter.write(NullWritable.get(), serializer.serialize(row))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ class SimpleTextOutputWriter(path: String, dataSchema: StructType, context: Task

private val writer = CodecStreams.createOutputStreamWriter(context, new Path(path))

override def init(): Unit = {}

override def write(row: InternalRow): Unit = {
val serialized = row.toSeq(dataSchema).map { v =>
if (v == null) "" else v.toString
Expand Down