|
17 | 17 |
|
18 | 18 | package org.apache.spark.sql.catalyst.expressions |
19 | 19 |
|
| 20 | +import com.univocity.parsers.csv.CsvParser |
| 21 | + |
20 | 22 | import org.apache.spark.sql.AnalysisException |
21 | 23 | import org.apache.spark.sql.catalyst.InternalRow |
| 24 | +import org.apache.spark.sql.catalyst.analysis.TypeCheckResult |
22 | 25 | import org.apache.spark.sql.catalyst.csv._ |
23 | 26 | import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback |
24 | 27 | import org.apache.spark.sql.catalyst.util._ |
@@ -120,3 +123,54 @@ case class CsvToStructs( |
120 | 123 |
|
121 | 124 | override def prettyName: String = "from_csv" |
122 | 125 | } |
| 126 | + |
| 127 | +/** |
| 128 | + * A function infers schema of CSV string. |
| 129 | + */ |
| 130 | +@ExpressionDescription( |
| 131 | + usage = "_FUNC_(csv[, options]) - Returns schema in the DDL format of CSV string.", |
| 132 | + examples = """ |
| 133 | + Examples: |
| 134 | + > SELECT _FUNC_('1,abc'); |
| 135 | + struct<_c0:int,_c1:string> |
| 136 | + """, |
| 137 | + since = "3.0.0") |
| 138 | +case class SchemaOfCsv( |
| 139 | + child: Expression, |
| 140 | + options: Map[String, String]) |
| 141 | + extends UnaryExpression with CodegenFallback { |
| 142 | + |
| 143 | + def this(child: Expression) = this(child, Map.empty[String, String]) |
| 144 | + |
| 145 | + def this(child: Expression, options: Expression) = this( |
| 146 | + child = child, |
| 147 | + options = ExprUtils.convertToMapData(options)) |
| 148 | + |
| 149 | + override def dataType: DataType = StringType |
| 150 | + |
| 151 | + override def nullable: Boolean = false |
| 152 | + |
| 153 | + @transient |
| 154 | + private lazy val csv = child.eval().asInstanceOf[UTF8String] |
| 155 | + |
| 156 | + override def checkInputDataTypes(): TypeCheckResult = child match { |
| 157 | + case Literal(s, StringType) if s != null => super.checkInputDataTypes() |
| 158 | + case _ => TypeCheckResult.TypeCheckFailure( |
| 159 | + s"The input csv should be a string literal and not null; however, got ${child.sql}.") |
| 160 | + } |
| 161 | + |
| 162 | + override def eval(v: InternalRow): Any = { |
| 163 | + val parsedOptions = new CSVOptions(options, true, "UTC") |
| 164 | + val parser = new CsvParser(parsedOptions.asParserSettings) |
| 165 | + val row = parser.parseLine(csv.toString) |
| 166 | + assert(row != null, "Parsed CSV record should not be null.") |
| 167 | + |
| 168 | + val header = row.zipWithIndex.map { case (_, index) => s"_c$index" } |
| 169 | + val startType: Array[DataType] = Array.fill[DataType](header.length)(NullType) |
| 170 | + val fieldTypes = CSVInferSchema.inferRowType(parsedOptions)(startType, row) |
| 171 | + val st = StructType(CSVInferSchema.toStructFields(fieldTypes, header, parsedOptions)) |
| 172 | + UTF8String.fromString(st.catalogString) |
| 173 | + } |
| 174 | + |
| 175 | + override def prettyName: String = "schema_of_csv" |
| 176 | +} |
0 commit comments