Skip to content

Fix Row unpickling for long values that fit in int - #1247

Open
mmustafasenoglu wants to merge 4 commits into
dotnet:mainfrom
mmustafasenoglu:fix/row-unpickles-long-value
Open

Fix Row unpickling for long values that fit in int#1247
mmustafasenoglu wants to merge 4 commits into
dotnet:mainfrom
mmustafasenoglu:fix/row-unpickles-long-value

Conversation

@mmustafasenoglu

@mmustafasenoglu mmustafasenoglu commented Aug 6, 2026

Copy link
Copy Markdown

Closes #27

When PySpark serializes a long value that fits in an int, the Pickler optimizes it and serializes as int. When .NET deserializes this boxed int, a direct unbox to long (or GetAs<long>()) throws an exception because the boxed type is int, not long.

This is exactly the scenario described in the issue — TimestampType.FromInternal() already has a workaround for this, but LongType did not.

Fix: Add NeedConversion() + FromInternal() to LongType so Row.Convert() coerces the unpickled value to match the schema type. This follows the same pattern already used by TimestampType.

Changes:

  • SimpleTypes.cs: Added NeedConversion() and FromInternal() to LongType (avoids re-boxing by returning obj directly when already a boxed long)
  • Row.cs: Removed the TODO comments that described this issue (now fixed)
  • RowTests.cs: Added RowGetAsLongFromPickledIntTest that reproduces the exact scenario from Row unpickles long value incorrectly. #27, plus Assert.Same test for already boxed long

Note: GetAs already supports this conversion on main via TypeConverter; the added behavior is normalization of raw Get() / Values and nested collections (ArrayType/MapType with LongType).

When PySpark serializes a long value that fits in an int, the Pickler
optimizes it and serializes as int. When .NET deserializes this boxed
int, a direct unbox to long (or GetAs<long>) throws an exception.

Fix by adding NeedConversion() + FromInternal() to LongType, IntegerType,
and ShortType so Row.Convert() coerces the unpickled value to match the
schema type. This follows the same pattern already used by TimestampType.

Also adds a test that reproduces the exact scenario from issue dotnet#27.
Copilot AI review requested due to automatic review settings August 6, 2026 08:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@mmustafasenoglu

Copy link
Copy Markdown
Author

@dotnet-policy-service agree

@SparkSnail

Copy link
Copy Markdown
Contributor

/AzurePipelines run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@mmustafasenoglu

Copy link
Copy Markdown
Author

@dotnet-policy-service agree

@SparkSnail

Copy link
Copy Markdown
Contributor

@mmustafasenoglu
Thanks for the contribution. I took a closer look and found that Row.GetAs<long> is already handled on the current main branch by TypeConverter.ConvertTo<T> / Convert.ChangeType from #1178.

The remaining behavior introduced by this PR is eager normalization of Row.Get() / Values, which is broader than issue #27.

The current pipeline also fails because IntegerType.NeedConversion() changes the behavior of ArrayType<IntegerType> and MapType<IntegerType, ...>. Since Integer values already unpickle as System.Int32, this conversion adds unnecessary traversal/allocation.

Could you please:

  • Narrow the change to LongType, or separately justify and test the Integer/Short behavior.
  • Remove the unnecessary IntegerType conversion.
  • Return the original object when it is already a long to avoid re-boxing.
  • Add LongType regression tests for raw Get() and nested Array/Map cases.
  • Check and fix the pipeline errors

Once the pipeline is green, we can review the narrowed change again.

Addresses review feedback on dotnet#1247:
- Remove NeedConversion/FromInternal from IntegerType and ShortType
  (integer values already unpickle as System.Int32, conversion is
  unnecessary and breaks ArrayType<IntegerType>/MapType<IntegerType>)
- Keep LongType coercion (the actual fix for dotnet#27)
- Add RowLongTypeInArrayTest and RowLongTypeInMapTest for nested cases
- LongType.FromInternal returns the original object when already long
  to avoid re-boxing
@mmustafasenoglu

Copy link
Copy Markdown
Author

Addressed review feedback:

  • Removed NeedConversion()/FromInternal() from IntegerType and ShortType — integer values already unpickle as System.Int32, so the conversion was unnecessary and broke ArrayType<IntegerType> / MapType<IntegerType>
  • Kept LongType coercion only (the actual fix for Row unpickles long value incorrectly. #27)
  • LongType.FromInternal returns the original object when already a long to avoid re-boxing
  • Added RowLongTypeInArrayTest and RowLongTypeInMapTest for nested Array/Map regression coverage

All 3 new tests pass locally. Pipeline should be green now.

@SparkSnail

Copy link
Copy Markdown
Contributor

Thanks for narrowing the change to LongType and adding the array/map coverage.
One small follow-up: return l still re-boxes the value because FromInternal returns object. Please return obj in that branch and add an Assert.Same test for an already boxed long.
Please also update the PR description to reflect the LongType-only change. GetAs already supports this conversion on main; the added behavior is normalization of raw Get() / Values and nested collections.
Once updated, I’ll trigger a fresh pipeline run.

When obj is already a boxed long, return obj directly instead of
unboxing to primitive long and re-boxing. This avoids unnecessary
allocation on the hot path.

Add Assert.Same test for already boxed long in RowTests.
…PickledIntTest

Verifies that when a boxed long is passed directly (not pickled as int),
it's returned as-is without re-boxing.
@SparkSnail

Copy link
Copy Markdown
Contributor

/AzurePipelines run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Row unpickles long value incorrectly.

3 participants