-
Notifications
You must be signed in to change notification settings - Fork 29.4k
[SPARK-57462][PYTHON][SQL] Add PySpark support for nanosecond-precision timestamp types #58418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
f8b74b1
7e3668c
2a45248
39b1116
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -320,11 +320,18 @@ def _to_pandas(self, **kwargs: Any) -> "PandasDataFrameLike": | |
|
|
||
| assert isinstance(self, DataFrame) | ||
|
|
||
| from pyspark.sql.pandas.types import _create_converter_to_pandas | ||
| from pyspark.sql.pandas.types import ( | ||
| _create_converter_to_pandas, | ||
| _reject_timestamp_nanos_conversion, | ||
| ) | ||
| from pyspark.sql.pandas.utils import require_minimum_pandas_version | ||
|
|
||
| require_minimum_pandas_version() | ||
|
|
||
| # Arrow/pandas value conversion for the nanosecond timestamp types is a pending follow-up; | ||
| # fail deterministically here rather than fall back to a lossy / wrong-timezone result. | ||
| _reject_timestamp_nanos_conversion(self.schema) | ||
|
|
||
| import pandas as pd | ||
|
|
||
| ( | ||
|
|
@@ -632,6 +639,16 @@ def createDataFrame( # type: ignore[misc] | |
| selfcheck = arrowSafeTypeConversion == "true" | ||
| infer_pandas_dict_as_map = inferPandasDictAsMap == "true" | ||
|
|
||
| # Building a DataFrame from pandas/PyArrow data goes through Arrow, whose nanosecond | ||
| # timestamp value conversion is a pending follow-up; fail deterministically for an | ||
| # explicit nanosecond-typed schema rather than silently mis-handle the values. Covers a | ||
| # bare atomic DataType schema as well as a StructType (a DDL string / list of names cannot | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The pandas createDataFrame comment says a DDL string cannot carry these types. Classic SparkSession.createDataFrame runs _parse_ddl first, so "ts timestamp_ntz(9)" is already a StructType when the pandas mixin runs, and the isinstance(schema, DataType) guard does fire. |
||
| # carry these types without an explicit DataType, so is left to the server to gate). | ||
| if isinstance(schema, DataType): | ||
| from pyspark.sql.pandas.types import _reject_timestamp_nanos_conversion | ||
|
|
||
| _reject_timestamp_nanos_conversion(schema) | ||
|
|
||
| if type(data).__name__ == "Table": | ||
| # `data` is a PyArrow Table | ||
| from pyspark.sql.pandas.utils import require_minimum_pyarrow_version | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ | |
| from pyspark.errors import PySparkTypeError, PySparkValueError, UnsupportedOperationException | ||
| from pyspark.loose_version import LooseVersion | ||
| from pyspark.sql.types import ( | ||
| AnyTimestampNanoType, | ||
| ArrayType, | ||
| BinaryType, | ||
| BooleanType, | ||
|
|
@@ -77,6 +78,45 @@ | |
| metadata_key = b"SPARK::metadata::json" | ||
|
|
||
|
|
||
| def _contains_timestamp_nanos(dt: DataType) -> bool: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. _contains_timestamp_nanos in pandas/types.py duplicates _has_type(..., AnyTimestampNanoType) in types.py. |
||
| """True if ``dt`` is, or structurally contains, a nanosecond-capable timestamp type. | ||
|
|
||
| The Arrow / pandas value conversion for :class:`TimestampNTZNanosType` / | ||
| :class:`TimestampLTZNanosType` is not implemented yet (planned follow-up). Rather than let | ||
| these paths silently mis-handle the value (wrong time zone for LTZ, or a leaked | ||
| ``pandas.Timestamp``), callers use this to fail deterministically; see | ||
| :func:`_reject_timestamp_nanos_conversion`. | ||
| """ | ||
| if isinstance(dt, AnyTimestampNanoType): | ||
| return True | ||
| elif isinstance(dt, ArrayType): | ||
| return _contains_timestamp_nanos(dt.elementType) | ||
| elif isinstance(dt, MapType): | ||
| return _contains_timestamp_nanos(dt.keyType) or _contains_timestamp_nanos(dt.valueType) | ||
| elif isinstance(dt, StructType): | ||
| return any(_contains_timestamp_nanos(f.dataType) for f in dt.fields) | ||
| elif isinstance(dt, UserDefinedType): | ||
| return _contains_timestamp_nanos(dt.sqlType()) | ||
| else: | ||
| return False | ||
|
|
||
|
|
||
| def _reject_timestamp_nanos_conversion(schema: DataType) -> None: | ||
| """Raise if ``schema`` involves a nanosecond timestamp type, for Arrow/pandas value paths. | ||
|
|
||
| Keeps the not-yet-supported Arrow/pandas/Connect data paths failing deterministically instead | ||
| of silently producing wrong values, consistent with :func:`to_arrow_type`, which already | ||
| rejects these types with the same error condition. | ||
| """ | ||
| from pyspark.errors import PySparkTypeError | ||
|
|
||
| if _contains_timestamp_nanos(schema): | ||
| raise PySparkTypeError( | ||
| errorClass="UNSUPPORTED_DATA_TYPE_FOR_ARROW_CONVERSION", | ||
| messageParameters={"data_type": str(schema)}, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. _reject_timestamp_nanos_conversion reports str(schema) (the whole struct). to_arrow_type reports the leaf. Same class of issue as the map-key message. |
||
| ) | ||
|
|
||
|
|
||
| def to_arrow_metadata(metadata: Optional[Dict[str, Any]] = None) -> Optional[Dict[bytes, bytes]]: | ||
| if metadata is not None and len(metadata) > 0: | ||
| return {metadata_key: json.dumps(metadata).encode("utf-8")} | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.