Implement Spark-compatible `variant_get` / `try_variant_get`
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 15/100
Hướng nghiên cứu
Start in crates/spark/src/function/variant/, reading the existing VARIANT functions and confirming the Arrow VARIANT representation from #273 has landed, since both functions depend on it. Then port Spark's VariantPathParser and VariantGet.cast dispatch, decoding values through parquet_variant::Variant, and register the UDF with return_field_from_args. Done means both names registered and documented with #[user_doc], plus crates/sqllogictest/test_files/variant/variant_get.slt covering the path grammar and cast edge cases listed above.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Part of #273 and apache/datafusion-comet#7300.
What the function does
variant_get(v, path[, type]) takes the sub-variant of v at path and casts it to type. try_variant_get
does the same, but a failed cast returns NULL instead of raising an error.
vmust beVARIANT.pathis aSTRING(any collation) and may be a column.typemust be a foldable string. It is parsed as DDL or JSON schema, so'int','array<int>'and
'struct<a int>'all work. CHAR/VARCHAR are rejected.- Without
typethe result isVARIANT. The result is always nullable, whatever nullabilitytypedeclares. - A NULL
vorpathreturns NULL. - Allowed target types are numerics, boolean, string, binary, date/timestamp/timestamp_ntz,
VARIANT, arrays of
these,MAP<STRING, ...>and structs. Anything else fails analysis with
DATATYPE_MISMATCH.CAST_WITHOUT_SUGGESTION. That includes intervals and maps with non-string keys. - Both functions were added in Spark 4.0.0. They depend on the VARIANT Arrow representation proposed in the
parse_jsonissue.
The cast code (VariantGet.cast) is shared with CAST(v AS type) / TRY_CAST and the v:path extraction
syntax, so implementing it here covers those too.
Examples
SELECT variant_get(parse_json('{"a": 1}'), '$.a', 'int');
-- 1
SELECT variant_get(parse_json('{"a": 1}'), '$.b', 'int');
-- NULL
SELECT variant_get(parse_json('[1, "2"]'), '$[1]', 'string');
-- 2
SELECT variant_get(parse_json('[1, "2"]'), '$[2]', 'string');
-- NULL
SELECT variant_get(parse_json('[1, "hello"]'), '$[1]');
-- "hello"
SELECT try_variant_get(parse_json('[1, "hello"]'), '$[1]', 'int');
-- NULL
SELECT variant_get(parse_json('{"a": 2147483648}'), '$.a', 'int');
-- error INVALID_VARIANT_CAST: The variant value `2147483648` cannot be cast into `"INT"`. ...
SELECT variant_get(parse_json('{"1": {"2": {"3": [4]}}}'), '$.1.2.3[0]', 'int');
-- 4
SELECT variant_get(parse_json('{"1": {"2": {"3": [4]}}}'), '$.1.2.3[''0'']', 'int');
-- NULL
SELECT variant_get(parse_json('1.10'), '$', 'string');
-- 1.1
SELECT try_variant_get(parse_json('[1, 2, 3, "hello"]'), '$', 'array<int>');
-- [1,2,3,null]
SELECT variant_get(parse_json('{"a": 1, "d": 2, "c": 3}'), '$', 'struct<a int, b int, c int>');
-- {"a":1,"b":null,"c":3}
These come from the ExpressionDescription and VariantExpressionSuite. The struct and array results there are
asserted as rows, so the display form shown is the usual Spark rendering.
Edge cases to test
- Path grammar (
VariantPathParser):- A path is
$followed by any number of.key,['key'],["key"]or[N]segments, with no whitespace
skipping. .keytakes any characters except.and[, so$.zip codeand$.fb:testidare valid.- Quoted keys cannot contain their own quote character, and there are no escapes.
- An empty key needs
['']or[""]. - Invalid paths raise
INVALID_VARIANT_GET_PATHwithfunctionName = `variant_get`, even in
try_variant_get. Examples are"",.a,$1,$[-1]and$['"]. For a literal path the error is raised
at first evaluation, not at analysis. $[99999999999]matches\d+and thentoIntoverflows. Pin down what Spark does there.
- A path is
- Lookups:
- Key lookup is case-sensitive (
$.Adoes not matcha). - A key segment on a non-object returns NULL, and so does an index segment on a non-array or out of range.
Cover objects with more than 32 fields (binary search) and more than 255 elements or fields.
- Key lookup is case-sensitive (
- JSON null versus SQL NULL:
- A variant null cast to any concrete type, including STRING, is SQL NULL.
- With no
type(VARIANT), the variant null is kept, sois_variant_null(variant_get(..., '$.a'))is true.
- A VARIANT result is re-encoded so it only carries the dictionary keys it uses. It is not a byte slice of the
input. - Scalar casts:
- A typed literal is built for the variant's type, then cast with Spark
Castin TRY mode, but only when
Cast.canAnsiCastallows the pair. A NULL result or a disallowed pair counts as an invalid cast. spark.sql.ansi.enabledhas no effect. Both settings throw forvariant_get.
- A typed literal is built for the variant's type, then cast with Spark
- Spark test expectations to copy:
"true"to boolean is true;" t "to boolean is true."true"to int is an error.truetodecimal(18,17)is 1, and todecimal(18,18)is an error.128to tinyint is an error.1.15todecimal(2,1)is 1.2 (HALF_UP).2147483647.999to int is 2147483647 (truncation)."inf"and"nan"to double.1E308to float is Infinity."1970-03-01"to date is 59.
- Timestamps:
- LONG and DECIMAL to TIMESTAMP are read as seconds with exact overflow checks:
9223372036854works and
9223372036855is an error. - Integer to TIMESTAMP_NTZ is an error.
- String to timestamp uses
spark.sql.session.timeZone.
- LONG and DECIMAL to TIMESTAMP are read as seconds with exact overflow checks:
- Decimal to string strips trailing zeros (
-1.00gives-1). Object or array to string gives the compact JSON
(Variant.toJson). Doubles print like JavaDouble.toString, for example1.0E19. Object or array to any
other scalar is an error. - UUID values (from external data) can only be cast to STRING.
- Nested targets:
- Struct fields are matched by exact name. Missing fields are NULL and extra fields are ignored.
- Maps take every object field.
- Arrays cast element by element.
variant_getfails on the first bad element.try_variant_getsets only that element to NULL, for example
Row(1, null).- The error's
valueis the JSON of the failing sub-value ("hello",{}), not the whole input.
- Mix literal and column
patharguments, and testvcoming fromparse_jsonand from columns.
Differences across Spark versions
- 3.x has no VARIANT.
- 4.0.4, 4.1.3 and 4.2.0 have the same
VariantGet, except that 4.2.0 matches CHAR/VARCHAR by type class instead
of by pattern. The behavior is the same.
Implementation notes
- Spark:
VariantGet,VariantGet.cast,VariantPathParserand the builders in
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/variant/variantExpressions.scala. Value
access is incommon/variant/src/main/java/org/apache/spark/types/variant/Variant.java(getFieldByKey,
getElementAtIndex,toJson). - arrow-rs
parquet_variant_compute::variant_getis a different design:- Its
VariantPathsyntax has no$and treats quoted brackets as fields. - Casting follows Arrow
CastOptions, not Spark's rules. - Its JSON output formats floats differently (
{f}, so1e10prints as10000000000).
Reuseparquet_variant::Variantfor decoding and the existing Spark cast kernels in datafusion-spark for
scalar conversions, but write the path parser, the cast dispatch andtoJsonto match Spark.
- Its
- DataFusion core has no VARIANT support.
- apache/datafusion-comet#5438 tracks native Variant support in Comet.
- Put this in
crates/spark/src/function/variant/with the other VARIANT functions. Becausetypedetermines the
return type, the UDF needsreturn_field_from_args. - Add
crates/sqllogictest/test_files/variant/variant_get.sltand document both names 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.
- Ngôn ngữ chính
- Rust
- Star
- 7
- Fork
- 15
- Merge trung bình
- 16 giờ 43 phút
- Pull request đã merge (30 ngày)
- 26
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của apache/datafusion-spark
-
enhancement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
apache/datafusion-spark#287 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement good first issue
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 80/100
apache/datafusion-spark#266 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[Bug] make_dt_interval returns NULL for more than about 1.49 million days, Spark returns the intervalCó thể đã có người làm @Subodh-17 đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
apache/datafusion-spark#109 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 18/100
apache/datafusion-spark#299 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 35/100
apache/datafusion-spark#298 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của apache/datafusion-spark
Issue tương tự
-
comp-rbac
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
ClickHouse/ClickHouse#131875 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug(python): read_transaction raises TypeError on a Rewrite with rewritten indicesCó thể đã có người làm @Borg93 đã nhận hôm nay. Đang mởbug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
lance-format/lance#11117 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
storytold/photocraft#15232 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
forge: fork preflight reads still send `requireCanonical: false`, which Rootstock rejects (-32603)Có thể đã có người làm @Ynyesto đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
foundry-rs/foundry#18485 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
bondybondbond/rushcut#238 · 1 bình luận ·