Implement Spark-compatible `from_csv`
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 22/100
Línea de trabajo
Start by reading crates/spark/src/function/csv/ (exists but empty) and the existing from_json implementation for the PERMISSIVE/FAILFAST pattern, then port Spark's csvExpressions.scala, UnivocityParser.scala and CSVExprUtils.scala semantics. Build a hand-written tokenizer mimicking univocity since arrow-csv does not fit, and compare against Comet's native/spark-expr/src/csv_funcs/to_csv.rs for option handling. Done means crates/sqllogictest/test_files/csv/from_csv.slt covers the listed edge cases and passes, with #[user_doc] documentation. This is a multi-week design-heavy port, not a starter task.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Part of #272 and apache/datafusion-comet#7300.
What the function does
from_csv(csvStr, schema[, options]) parses one CSV record into a STRUCT with the given schema. schema must be
a foldable string: a DDL string ('a INT, b DOUBLE') or schema_of_csv(...). A non-string schema fails with
INVALID_SCHEMA.NON_STRING_LITERAL, a non-struct type ('Array<int>') fails with INVALID_SCHEMA.NON_STRUCT_TYPE.
options must be a map(...) of string literals (INVALID_OPTIONS.NON_MAP_FUNCTION /
INVALID_OPTIONS.NON_STRING_TYPE). Option keys are case-insensitive. Field types can be any atomic type
(numeric, decimal, boolean, string, binary, date, timestamp, timestamp_ntz, intervals; VARIANT from 4.1, TIME
from 4.2). ARRAY, MAP and STRUCT fields fail at execution with UNSUPPORTED_DATATYPE.
The return type is schema.asNullable and the result is NULL only when the input is NULL. Since 3.0.0. The
function is time-zone aware (spark.sql.session.timeZone, or a timeZone option).
Parsing uses the univocity CsvParser configured from CSVOptions, wrapped in FailureSafeParser. Only
PERMISSIVE (default) and FAILFAST are allowed. DROPMALFORMED fails with PARSE_MODE_UNSUPPORTED.
The options that matter, with defaults: sep/delimiter (,, may be several characters, with \t-style escapes),
quote ("), escape (\), charToEscapeQuoteEscaping (unset), comment (disabled), nullValue (""),
emptyValue (""), nanValue (NaN), positiveInf (Inf), negativeInf (-Inf), ignoreLeadingWhiteSpace
and ignoreTrailingWhiteSpace (both false on read), mode, columnNameOfCorruptRecord
(spark.sql.columnNameOfCorruptRecord, default _corrupt_record), dateFormat, timestampFormat,
timestampNTZFormat, timeFormat (4.2), locale (en-US), enableDateTimeParsingFallback, maxColumns
(20480), maxCharsPerColumn (-1), unescapedQuoteHandling (STOP_AT_DELIMITER). lineSep is overridden with
\uFFFF, so an embedded newline is part of the field.
Examples
SELECT from_csv('1, 0.8', 'a INT, b DOUBLE');
-- {"a":1,"b":0.8}
SELECT from_csv('1, 3.14', 'a INT, f FLOAT');
-- {"a":1,"f":3.14}
SELECT from_csv('26/08/2015', 'time Timestamp', map('timestampFormat', 'dd/MM/yyyy'));
-- {"time":2015-08-26 00:00:00}
SELECT from_csv('1,abc', schema_of_csv('1,abc'));
-- {"_c0":1,"_c1":"abc"}
SELECT from_csv('14-30-45.123456', 'time TIME(6)', map('timeFormat', 'HH-mm-ss.SSSSSS')); -- 4.2
-- {"time":14:30:45.123456}
SELECT from_csv('25:00:00', 'time TIME'); -- 4.2
-- {"time":null}
SELECT from_csv(null, 'time TIME');
-- NULL
The examples above come from the ExpressionDescription and csv-functions.sql.out. The cases below come from
CsvExpressionsSuite and CsvFunctionsSuite (DataFrame API, shown here as SQL):
SELECT from_csv('---', 'a DOUBLE'); -- {"a":null}
SELECT from_csv(' ', 'a INT'); -- {"a":null}
SELECT from_csv('1', 'a INT, b INT'); -- {"a":1,"b":null}
SELECT from_csv('1,,"foo"', 'a BIGINT, b STRING, c STRING'); -- {"a":1,"b":null,"c":"foo"}
SELECT from_csv('"#""', 'str STRING', map('escape', '#')); -- str is a single `"` character
SELECT from_csv('# This line is commented', 'str STRING', map('comment', '#')); -- {"str":null}
SELECT from_csv(' a ', 'str STRING', map('ignoreLeadingWhiteSpace', 'true')); -- {"str":"a "}
SELECT from_csv(' a ', 'str STRING', map('ignoreTrailingWhiteSpace', 'true')); -- {"str":" a"}
SELECT from_csv('-', 'str STRING', map('nullValue', '-')); -- {"str":null}
SELECT from_csv('#', 'f FLOAT', map('nanValue', '#')); -- {"f":NaN}
SELECT from_csv('a,b,c,d', 'a STRING, b STRING, _corrupt_record STRING'); -- _corrupt_record = 'a,b,c,d' (a, b = 'a', 'b' by code reading)
SELECT from_csv('0,2013-111-11 12:13:14', 'a INT, b DATE, _unparsed STRING',
map('columnNameOfCorruptRecord', '_unparsed')); -- {"a":0,"b":null,"_unparsed":"0,2013-111-11 12:13:14"} (timeParserPolicy CORRECTED)
SELECT from_csv('2021-08-12T15:16:23.000+11:00', 'a TIMESTAMP_NTZ'); -- {"a":null}
Edge cases to test
- NULL input gives NULL. Any non-NULL input gives a non-NULL struct, even when every field is NULL.
- PERMISSIVE partial results: a field that fails to convert becomes NULL and the other fields keep their values.
A token count that differs from the schema marks the record malformed, but the tokens present are still
converted (missing ones are NULL, extra ones are ignored). The corrupt-record column (must be a nullable STRING,
otherwiseINVALID_CORRUPT_RECORD_TYPE) gets the raw input only when the record is malformed. - FAILFAST fails with
MALFORMED_RECORD_IN_PARSING.WITHOUT_SUGGESTION. For input"and schema
a INT, b INT, _unparsed STRING,badRecordis[null,null,"]. For1,\u0001\u0000\u0001234witha INT, b INT
it is[1,null]. nullValuedefaults to the empty string, so an empty field and a quoted empty field ("", read asemptyValue,
also"") are both NULL even for STRING fields (by code reading ofnullSafeDatum).- Whitespace is not trimmed by default. Numeric conversion uses the Java parsers:
Integer.parseIntand
Long.parseLongreject' 2', butFloat.parseFloatandDouble.parseDoubletrim, which is why' 3.14'works
above. Java float syntax also accepts1d,1f, hex floats andInfinity. - BOOLEAN uses Scala
toBoolean(case-insensitivetrue/falseonly). DECIMAL with the defaulten-USlocale
strips every,beforenew BigDecimal(...), so a quoted"1,000"parses as 1000. Other locales use
DecimalFormat. Values that do not fit the precision are NULL. - Date and timestamp: without
dateFormat/timestampFormatthe parse falls back to the lenient
stringToDate/stringToTimestamp(cast rules). With a pattern set, no fallback unless
enableDateTimeParsingFallbackorspark.sql.legacy.csv.enableDateTimeParsingFallbackis true.
spark.sql.legacy.timeParserPolicychanges results: for2013-111-11 12:13:14as DATE, CORRECTED gives NULL,
LEGACY gives2022-03-11, EXCEPTION fails withINCONSISTENT_BEHAVIOR_CROSS_VERSION.PARSE_DATETIME_BY_NEW_PARSER.
A string with an offset (Z) ignores the session time zone.localeaffects month names (dd MMM yyyy HH:mm). - Options: a
quote/escape/commentlonger than one character fails with
OPTION_VALUE_EXCEEDS_ONE_CHARACTER. An empty one means\u0000. Delimiter errors areINVALID_DELIMITER_VALUE.*
(EMPTY_STRING,SINGLE_BACKSLASH,UNSUPPORTED_SPECIAL_CHARACTER,DELIMITER_LONGER_THAN_EXPECTED).
maxCharsPerColumn= 2 on12345fails with a message containing
Length of parsed input (3) exceeds the maximum number of characters defined in your parser settings (2). - Univocity quirks: an embedded newline stays in the field, because
lineSepis forced to\uFFFF. Input
1,2\n2with schemaschema_of_csv('1,2\n2')(_c0 INT, _c1 STRING) gives{1, "2\n2"}(SPARK-38955 test).
Unbalanced quotes followunescapedQuoteHandling(defaultSTOP_AT_DELIMITER). Invalid UTF-8 input gives a
struct of NULLs (from_csv bad UTF-8test). - Scalar and column input; non-default
spark.sql.session.timeZone; bad UTF-8; duplicate field names in the schema.
Differences across Spark versions
- 3.4 / 3.5:
spark.sql.legacy.timeParserPolicydefaults toEXCEPTION, so some malformed dates raise
INCONSISTENT_BEHAVIOR_CROSS_VERSION.PARSE_DATETIME_BY_NEW_PARSERinstead of giving NULL. 4.0 changes the default
toCORRECTED. - 4.0: input accepts collated strings (
StringTypeWithCollation, including trim collations). Evaluation moved into
CsvToStructsEvaluatorwith codegen, with no change in results. - 4.1:
VARIANTfield type and thesingleVariantColumnoption. - 4.2:
TIME(p)field type and thetimeFormatoption.
Implementation notes
- Spark:
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/csvExpressions.scala
(CsvToStructs),expressions/csv/CsvExpressionEvalUtils.scala(CsvToStructsEvaluator),
catalyst/csv/CSVOptions.scala,catalyst/csv/UnivocityParser.scala,catalyst/csv/CSVExprUtils.scala
(delimiter parsing),catalyst/util/FailureSafeParser.scala. - DataFusion core has no
from_csv. Thearrow-csvreader is a file reader with different quoting, null and type
rules, so a hand-written tokenizer that mimics univocity's settings is likely needed. to_csvis tracked in apache/datafusion-spark#138 (port from Comet). Comet's nativeto_csv
(native/spark-expr/src/csv_funcs/to_csv.rs,csv_write_options.rs) already modelsdelimiter,quote,
escape,nullValue,quoteAlland the whitespace flags. A sharedCsvOptionsstruct (read and write defaults
differ: whitespace flags aretrueon write,emptyValueis""quoted on write) should live in
crates/spark/src/function/csv/, which exists but is empty today.- Reuse the Spark date/timestamp parsing helpers in datafusion-spark if present, and keep
from_json's
PERMISSIVE / FAILFAST handling consistent if both are implemented. - Tests: add
crates/sqllogictest/test_files/csv/from_csv.slt; document with#[user_doc].
This issue was generated by an LLM (Claude) from the Apache Spark source code and tests. Examples come from Spark's ExpressionDescription examples, test suites and .sql.out golden files, but please verify them against Spark before relying on them.
- Lenguaje dominante
- Rust
- Estrellas
- 7
- Forks
- 15
- Merge medio
- 16 h 43 min
- PR fusionados (30 d)
- 26
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de apache/datafusion-spark
-
enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
apache/datafusion-spark#287 ·
Los mantenedores suelen responder en 1 día
-
enhancement good first issue
Dificultad 2/5 1-3 horas Aptitud para principiantes 80/100
apache/datafusion-spark#266 ·
Los mantenedores suelen responder en 1 día
-
[Bug] make_dt_interval returns NULL for more than about 1.49 million days, Spark returns the intervalPosiblemente ocupada @Subodh-17 la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
apache/datafusion-spark#109 ·
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 5/5 Más de una semana Aptitud para principiantes 18/100
apache/datafusion-spark#299 ·
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
apache/datafusion-spark#298 ·
Los mantenedores suelen responder en 1 día
Todos los issues de apache/datafusion-spark
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
storytold/photocraft#15232 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
suhina-daw/suhina-daw#1477 ·
Los mantenedores suelen responder en 1 día
-
parity priority: low ui
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
storytold/pdfcraft#7483 · 1 comentario ·
Los mantenedores suelen responder en 2 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
IngvarConsulting/unica#3097 ·
Los mantenedores suelen responder en 1 día
-
enhancement in-progress
Dificultad 1/5 Menos de una hora Aptitud para principiantes 64/100