[ 
https://issues.apache.org/jira/browse/SPARK-60279?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Shuheng Ma updated SPARK-60279:
-------------------------------
    Description: 
h3. Description
In Spark Connect, {{GroupedData.pivot(pivot_col, values)}} only accepts values 
of type bool, float, int or str. Values that classic PySpark accepts, such as 
{{None}}, {{datetime.date}}, {{datetime.datetime}} and {{decimal.Decimal}}, 
raise {{NOT_EXPECTED_TYPE}}. A common way to hit this is to collect the pivot 
values with {{distinct()}}: if the column has a NULL, the list contains 
{{None}}.

h3. Reproducer
{code:python}
# spark = SparkSession.builder.remote("local[2]").getOrCreate() for Connect, 
.master("local[2]") for classic
import datetime, decimal
df = spark.createDataFrame(
    [(1, "a", datetime.date(2020, 1, 1), decimal.Decimal("1.5"), 10),
     (2, None, datetime.date(2020, 1, 2), decimal.Decimal("2.5"), 20)],
    "k int, s string, d date, dec decimal(10,1), v int")

vals = [r[0] for r in df.select("s").distinct().collect()]   # contains None
df.groupBy("k").pivot("s", vals).sum("v").columns
# classic: ['k', 'a', 'null']
# Connect: PySparkTypeError: [NOT_EXPECTED_TYPE] Argument `value` should be 
bool, float, int or str, got NoneType.

df.groupBy("k").pivot("d", [datetime.date(2020, 1, 1)]).sum("v").columns
# classic: ['k', '2020-01-01']
# Connect: PySparkTypeError: [NOT_EXPECTED_TYPE] Argument `value` should be 
bool, float, int or str, got date.

df.groupBy("k").pivot("dec", [decimal.Decimal("1.5")]).sum("v").columns
# classic: ['k', '1.5']
# Connect: PySparkTypeError: [NOT_EXPECTED_TYPE] Argument `value` should be 
bool, float, int or str, got Decimal.
{code}

h3. Cause
{{GroupedData.pivot}} in {{python/pyspark/sql/connect/group.py}} checks each 
value with {{isinstance(v, (bool, float, int, str))}}. The allowed types match 
the {{LiteralType}} type hint rather than what {{F.lit}} supports. The values 
are later turned into literals with {{F.lit}}, which handles None, date, 
datetime and Decimal, so the check rejects values that would work. The check 
has been there since the Connect pivot was added (SPARK-41693, 3.4.0).
Code: 
https://github.com/apache/spark/blob/d18b51873dbcb8f18677b9a5860b3131f16b1d8e/python/pyspark/sql/connect/group.py#L248-L257

h3. Suggested fix
Also accept None, datetime.date (which covers datetime.datetime) and 
decimal.Decimal in that check. Nested lists and dicts keep the current error; 
classic rejects them too. Classic also accepts bytes, bytearray, datetime.time, 
datetime.timedelta, NumPy scalars such as np.int64, np.bool_ and np.float32, 
and a tuple of values; this fix does not cover them. Another option is to drop 
the per-value check and let {{F.lit}} validate, as classic leaves it to the 
JVM: that also covers bytes, time, timedelta and NumPy scalars, but a Column or 
a nested list would then fail on the server with 
{{CONNECT_INVALID_PLAN.UNSUPPORTED_LITERAL_TYPE}} instead of on the client.

h3. Who hits it
A feature pipeline on Databricks serverless (Spark Connect) hit the None case 
and worked around it by dropping None from the values, which loses the "null" 
column that classic would produce: 
https://github.com/WenjieAtBayer/kerendia_nba_3_1_develop/pull/3

Tested on PySpark 4.2.0 from PyPI and on master (5.0.0-SNAPSHOT), Python 3.12, 
with a local Spark Connect server (first line of the reproducer). I have a fix 
with tests ready and will open a PR shortly.

Co-developed with Claude Code (Claude Opus 5.5): the investigation, the 
reproducer and this description.
Generated-by: Claude Code (Claude Opus 5.5)


  was:
h3. Description
In Spark Connect, {{GroupedData.pivot(pivot_col, values)}} only accepts values 
of type bool, float, int or str. Values that classic PySpark accepts, such as 
{{None}}, {{datetime.date}}, {{datetime.datetime}} and {{decimal.Decimal}}, 
raise {{NOT_EXPECTED_TYPE}}. A common way to hit this is to collect the pivot 
values with {{distinct()}}: if the column has a NULL, the list contains 
{{None}}.

h3. Reproducer
{code:python}
# spark = SparkSession.builder.remote("local[2]").getOrCreate() for Connect, 
.master("local[2]") for classic
import datetime, decimal
df = spark.createDataFrame(
    [(1, "a", datetime.date(2020, 1, 1), decimal.Decimal("1.5"), 10),
     (2, None, datetime.date(2020, 1, 2), decimal.Decimal("2.5"), 20)],
    "k int, s string, d date, dec decimal(10,1), v int")

vals = [r[0] for r in df.select("s").distinct().collect()]   # contains None
df.groupBy("k").pivot("s", vals).sum("v").columns
# classic: ['k', 'a', 'null']
# Connect: PySparkTypeError: [NOT_EXPECTED_TYPE] Argument `value` should be 
bool, float, int or str, got NoneType.

df.groupBy("k").pivot("d", [datetime.date(2020, 1, 1)]).sum("v").columns
# classic: ['k', '2020-01-01']
# Connect: PySparkTypeError: [NOT_EXPECTED_TYPE] Argument `value` should be 
bool, float, int or str, got date.

df.groupBy("k").pivot("dec", [decimal.Decimal("1.5")]).sum("v").columns
# classic: ['k', '1.5']
# Connect: PySparkTypeError: [NOT_EXPECTED_TYPE] Argument `value` should be 
bool, float, int or str, got Decimal.
{code}

h3. Cause
{{GroupedData.pivot}} in {{python/pyspark/sql/connect/group.py}} checks each 
value with {{isinstance(v, (bool, float, int, str))}}. The allowed types match 
the {{LiteralType}} type hint rather than what {{F.lit}} supports. The values 
are later turned into literals with {{F.lit}}, which handles None, date, 
datetime and Decimal, so the check rejects values that would work. The check 
has been there since the Connect pivot was added (SPARK-41693, 3.4.0).
Code: 
https://github.com/apache/spark/blob/d18b51873dbcb8f18677b9a5860b3131f16b1d8e/python/pyspark/sql/connect/group.py#L248-L257

h3. Suggested fix
Also accept None, datetime.date (which covers datetime.datetime) and 
decimal.Decimal in that check. Nested lists and dicts keep the current error; 
classic rejects them too. Classic also accepts bytes, bytearray, datetime.time, 
datetime.timedelta, NumPy scalars such as np.int64, np.bool_ and np.float32, 
and a tuple of values; this fix does not cover them. Another option is to drop 
the per-value check and let {{F.lit}} validate, as classic leaves it to the 
JVM: that also covers bytes, time, timedelta and NumPy scalars, but a Column or 
a nested list would then fail on the server with 
{{CONNECT_INVALID_PLAN.UNSUPPORTED_LITERAL_TYPE}} instead of on the client.

h3. Who hits it
A feature pipeline on Databricks serverless (Spark Connect) hit the None case 
and worked around it by dropping None from the values, which loses the "null" 
column that classic would produce: 
https://github.com/WenjieAtBayer/kerendia_nba_3_1_develop/pull/3

Tested on PySpark 4.2.0 from PyPI and on master (5.0.0-SNAPSHOT), Python 3.12, 
with a local Spark Connect server (first line of the reproducer). I have a fix 
with tests ready and will open a PR shortly.

Co-developed with Claude Code (Claude Opus 5.5): the investigation, the 
reproducer and this description.
Generated-by: Claude Code (Claude Opus 5.5)


> Spark Connect GroupedData.pivot rejects None, date and Decimal pivot values 
> that classic accepts
> ------------------------------------------------------------------------------------------------
>
>                 Key: SPARK-60279
>                 URL: https://issues.apache.org/jira/browse/SPARK-60279
>             Project: Spark
>          Issue Type: Bug
>          Components: Connect, PySpark
>    Affects Versions: 4.2.0, 5.0.0
>            Reporter: Shuheng Ma
>            Priority: Minor
>
> h3. Description
> In Spark Connect, {{GroupedData.pivot(pivot_col, values)}} only accepts 
> values of type bool, float, int or str. Values that classic PySpark accepts, 
> such as {{None}}, {{datetime.date}}, {{datetime.datetime}} and 
> {{decimal.Decimal}}, raise {{NOT_EXPECTED_TYPE}}. A common way to hit this is 
> to collect the pivot values with {{distinct()}}: if the column has a NULL, 
> the list contains {{None}}.
> h3. Reproducer
> {code:python}
> # spark = SparkSession.builder.remote("local[2]").getOrCreate() for Connect, 
> .master("local[2]") for classic
> import datetime, decimal
> df = spark.createDataFrame(
>     [(1, "a", datetime.date(2020, 1, 1), decimal.Decimal("1.5"), 10),
>      (2, None, datetime.date(2020, 1, 2), decimal.Decimal("2.5"), 20)],
>     "k int, s string, d date, dec decimal(10,1), v int")
> vals = [r[0] for r in df.select("s").distinct().collect()]   # contains None
> df.groupBy("k").pivot("s", vals).sum("v").columns
> # classic: ['k', 'a', 'null']
> # Connect: PySparkTypeError: [NOT_EXPECTED_TYPE] Argument `value` should be 
> bool, float, int or str, got NoneType.
> df.groupBy("k").pivot("d", [datetime.date(2020, 1, 1)]).sum("v").columns
> # classic: ['k', '2020-01-01']
> # Connect: PySparkTypeError: [NOT_EXPECTED_TYPE] Argument `value` should be 
> bool, float, int or str, got date.
> df.groupBy("k").pivot("dec", [decimal.Decimal("1.5")]).sum("v").columns
> # classic: ['k', '1.5']
> # Connect: PySparkTypeError: [NOT_EXPECTED_TYPE] Argument `value` should be 
> bool, float, int or str, got Decimal.
> {code}
> h3. Cause
> {{GroupedData.pivot}} in {{python/pyspark/sql/connect/group.py}} checks each 
> value with {{isinstance(v, (bool, float, int, str))}}. The allowed types 
> match the {{LiteralType}} type hint rather than what {{F.lit}} supports. The 
> values are later turned into literals with {{F.lit}}, which handles None, 
> date, datetime and Decimal, so the check rejects values that would work. The 
> check has been there since the Connect pivot was added (SPARK-41693, 3.4.0).
> Code: 
> https://github.com/apache/spark/blob/d18b51873dbcb8f18677b9a5860b3131f16b1d8e/python/pyspark/sql/connect/group.py#L248-L257
> h3. Suggested fix
> Also accept None, datetime.date (which covers datetime.datetime) and 
> decimal.Decimal in that check. Nested lists and dicts keep the current error; 
> classic rejects them too. Classic also accepts bytes, bytearray, 
> datetime.time, datetime.timedelta, NumPy scalars such as np.int64, np.bool_ 
> and np.float32, and a tuple of values; this fix does not cover them. Another 
> option is to drop the per-value check and let {{F.lit}} validate, as classic 
> leaves it to the JVM: that also covers bytes, time, timedelta and NumPy 
> scalars, but a Column or a nested list would then fail on the server with 
> {{CONNECT_INVALID_PLAN.UNSUPPORTED_LITERAL_TYPE}} instead of on the client.
> h3. Who hits it
> A feature pipeline on Databricks serverless (Spark Connect) hit the None case 
> and worked around it by dropping None from the values, which loses the "null" 
> column that classic would produce: 
> https://github.com/WenjieAtBayer/kerendia_nba_3_1_develop/pull/3
> Tested on PySpark 4.2.0 from PyPI and on master (5.0.0-SNAPSHOT), Python 
> 3.12, with a local Spark Connect server (first line of the reproducer). I 
> have a fix with tests ready and will open a PR shortly.
> Co-developed with Claude Code (Claude Opus 5.5): the investigation, the 
> reproducer and this description.
> Generated-by: Claude Code (Claude Opus 5.5)



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to