blob: 47d5644855e41d91df5d79380309adebbbffc51a [file]
"""pyspark.sql.functions module exposing Rust-backed SQL functions."""
import os
from pyspark import _pyspark
_functions = getattr(_pyspark, 'functions', None)
if _functions is None:
raise ImportError("_pyspark.functions not found")
# Get the native functions
_pyfunc_col = _functions.pyfunc_col
_pyfunc_lit = _functions.pyfunc_lit
_pyfunc_expr = _functions.pyfunc_expr
_pyfunc_sum = _functions.pyfunc_sum
_pyfunc_count = _functions.pyfunc_count
_pyfunc_avg = _functions.pyfunc_avg
_pyfunc_max = _functions.pyfunc_max
_pyfunc_min = _functions.pyfunc_min
_pyfunc_when = _functions.pyfunc_when
_call_function = _functions.pyfunc_call_function
# Mixed function bindings
_pyfunc_sha2 = _functions.pyfunc_sha2
_pyfunc_window = _functions.pyfunc_window
_pyfunc_window_with_slide_and_start = _functions.pyfunc_window_with_slide_and_start
_pyfunc_from_avro = _functions.pyfunc_from_avro
_pyfunc_from_avro_with_options = _functions.pyfunc_from_avro_with_options
_pyfunc_to_avro_with_schema = _functions.pyfunc_to_avro_with_schema
_pyfunc_from_protobuf = _functions.pyfunc_from_protobuf
_pyfunc_from_protobuf_with_descriptor = _functions.pyfunc_from_protobuf_with_descriptor
_pyfunc_from_protobuf_with_descriptor_and_options = _functions.pyfunc_from_protobuf_with_descriptor_and_options
_pyfunc_from_protobuf_with_options = _functions.pyfunc_from_protobuf_with_options
_pyfunc_to_protobuf = _functions.pyfunc_to_protobuf
_pyfunc_to_protobuf_with_descriptor = _functions.pyfunc_to_protobuf_with_descriptor
_pyfunc_to_protobuf_with_descriptor_and_options = _functions.pyfunc_to_protobuf_with_descriptor_and_options
_pyfunc_to_protobuf_with_options = _functions.pyfunc_to_protobuf_with_options
# Utility functions for wrapping/unwrapping columns
def _unwrap(obj):
"""Convert a Column to its internal representation for passing to _call_function."""
return obj
def _to_col(obj):
"""Coerce a ``ColumnOrName`` argument: a str is resolved to a column via col()."""
if isinstance(obj, str):
return _pyfunc_col(obj)
return obj
def _col_wrapper(native):
"""Wrap a native single-column function so it accepts a str column name too."""
def wrapper(col):
return native(_to_col(col))
return wrapper
def _wrap(obj):
"""Wrap the result from _call_function as a Column."""
return obj
# Auto-derived from PySpark 4.2.0 signatures: for generated functions, which positional
# args are ColumnOrName (a bare str is a COLUMN NAME). Functions NOT listed here have
# ALL args as columns. Value = (column_positions, variadic_col_start_or_None).
_FN_COL_ARGS = {
'any_value': ((0,), None),
'approxCountDistinct': ((0,), None),
'approx_count_distinct': ((0,), None),
'approx_percentile': ((0,), None),
'array_append': ((0,), None),
'array_contains': ((0,), None),
'array_insert': ((0, 1), None),
'array_join': ((0,), None),
'array_position': ((0,), None),
'array_prepend': ((0,), None),
'array_remove': ((0,), None),
'array_sort': ((0,), None),
'assert_true': ((0,), None),
'bround': ((0,), None),
'bucket': ((1,), None),
'ceil': ((0,), None),
'ceiling': ((0,), None),
'collate': ((0,), None),
'concat_ws': ((), 1),
'conv': ((0,), None),
'convert_timezone': ((2,), None),
'count_min_sketch': ((0,), None),
'curdate': ((), None),
'current_catalog': ((), None),
'current_database': ((), None),
'current_date': ((), None),
'current_path': ((), None),
'current_schema': ((), None),
'current_time': ((), None),
'current_timestamp': ((), None),
'current_timezone': ((), None),
'current_user': ((), None),
'date_format': ((0,), None),
'date_part': ((1,), None),
'date_trunc': ((1,), None),
'datepart': ((1,), None),
'decode': ((0,), None),
'dense_rank': ((), None),
'e': ((), None),
'element_at': ((0,), None),
'encode': ((0,), None),
'extract': ((1,), None),
'first': ((0,), None),
'first_value': ((0,), None),
'floor': ((0,), None),
'format_number': ((0,), None),
'format_string': ((), 1),
'from_csv': ((0,), None),
'from_json': ((0,), None),
'from_unixtime': ((0,), None),
'from_utc_timestamp': ((0,), None),
'from_xml': ((0,), None),
'get_json_object': ((0,), None),
'histogram_numeric': ((0,), None),
'hll_sketch_agg': ((0,), None),
'hll_union': ((0, 1), None),
'hll_union_agg': ((0,), None),
'ilike': ((0, 1), None),
'input_file_block_length': ((), None),
'input_file_block_start': ((), None),
'input_file_name': ((), None),
'instr': ((0,), None),
'kll_merge_agg_bigint': ((0,), None),
'kll_merge_agg_double': ((0,), None),
'kll_merge_agg_float': ((0,), None),
'kll_sketch_agg_bigint': ((0,), None),
'kll_sketch_agg_double': ((0,), None),
'kll_sketch_agg_float': ((0,), None),
'lag': ((0,), None),
'last': ((0,), None),
'last_value': ((0,), None),
'lead': ((0,), None),
'levenshtein': ((0, 1), None),
'like': ((0, 1), None),
'listagg': ((0,), None),
'listagg_distinct': ((0,), None),
'localtimestamp': ((), None),
'locate': ((1,), None),
'lpad': ((0,), None),
'map_contains_key': ((0,), None),
'max_by': ((0, 1), None),
'min_by': ((0, 1), None),
'mode': ((0,), None),
'monotonically_increasing_id': ((), None),
'months_between': ((0, 1), None),
'next_day': ((0,), None),
'now': ((), None),
'nth_value': ((0,), None),
'ntile': ((), None),
'percent_rank': ((), None),
'percentile': ((0,), None),
'percentile_approx': ((0,), None),
'pi': ((), None),
'raise_error': ((), None),
'rand': ((), None),
'randn': ((), None),
'randstr': ((), None),
'rank': ((), None),
'regexp_extract': ((0,), None),
'regexp_extract_all': ((0, 1), None),
'regexp_instr': ((0, 1), None),
'regexp_replace': ((0,), None),
'round': ((0,), None),
'row_number': ((), None),
'rpad': ((0,), None),
'schema_of_csv': ((), None),
'schema_of_json': ((), None),
'schema_of_xml': ((), None),
'session_user': ((), None),
'session_window': ((0,), None),
'shiftLeft': ((0,), None),
'shiftRight': ((0,), None),
'shiftRightUnsigned': ((0,), None),
'shiftleft': ((0,), None),
'shiftright': ((0,), None),
'shiftrightunsigned': ((0,), None),
'shuffle': ((0,), None),
'sort_array': ((0,), None),
'spark_partition_id': ((), None),
'split': ((0, 2), None),
'string_agg': ((0,), None),
'string_agg_distinct': ((0,), None),
'substring_index': ((0,), None),
'theta_sketch_agg': ((0,), None),
'theta_union': ((0, 1), None),
'theta_union_agg': ((0,), None),
'time_bucket': ((1,), None),
'timestamp_add': ((1, 2), None),
'timestamp_diff': ((1, 2), None),
'to_avro': ((0,), None),
'to_csv': ((0,), None),
'to_date': ((0,), None),
'to_json': ((0,), None),
'to_timestamp': ((0,), None),
'to_utc_timestamp': ((0,), None),
'to_xml': ((0,), None),
'translate': ((0,), None),
'trunc': ((0,), None),
'try_to_date': ((0,), None),
'try_variant_get': ((0,), None),
'tuple_intersection_agg_double': ((0,), None),
'tuple_intersection_agg_integer': ((0,), None),
'tuple_intersection_double': ((0, 1), None),
'tuple_intersection_integer': ((0, 1), None),
'tuple_intersection_theta_double': ((0, 1), None),
'tuple_intersection_theta_integer': ((0, 1), None),
'tuple_sketch_agg_double': ((0, 1), None),
'tuple_sketch_agg_integer': ((0, 1), None),
'tuple_sketch_summary_double': ((0,), None),
'tuple_sketch_summary_integer': ((0,), None),
'tuple_union_agg_double': ((0,), None),
'tuple_union_agg_integer': ((0,), None),
'tuple_union_double': ((0, 1), None),
'tuple_union_integer': ((0, 1), None),
'tuple_union_theta_double': ((0, 1), None),
'tuple_union_theta_integer': ((0, 1), None),
'uniform': ((), None),
'unix_timestamp': ((0,), None),
'user': ((), None),
'uuid': ((), None),
'variant_get': ((0,), None),
'version': ((), None),
'when': ((), None),
}
class _UnsetType:
"""Sentinel for optional generated-function args that were not supplied.
Generated wrappers carry PySpark's real parameter names (so keyword calls work),
but default every optional parameter to ``_UNSET`` and forward only the args the
caller actually passed. This reproduces the historical ``*args`` dispatch exactly
(same columns forwarded, in the same order) while accepting keyword arguments.
"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __repr__(self):
return "<unset>"
_UNSET = _UnsetType()
def _dispatch(fname, args):
"""Convert positional args per the ColumnOrName contract and dispatch to the core.
A bare ``str`` at a column position is a COLUMN NAME (resolved via ``_to_col``);
literal-position args (a format/pattern string, an int count, ...) pass through
unchanged. Functions absent from ``_FN_COL_ARGS`` have every positional arg as a
column. See ``_FN_COL_ARGS``.
"""
override = _FN_COL_ARGS.get(fname)
if override is None:
conv = [_to_col(a) for a in args]
else:
cols, vstart = override
conv = [
_to_col(a) if (i in cols or (vstart is not None and i >= vstart)) else a
for i, a in enumerate(args)
]
return _wrap(_call_function(fname, *conv))
def _create_wrapper(fname):
"""Fallback wrapper for a generated function with no reference PySpark signature.
Used only for functions absent from the reference (e.g. Rust-only extras); these
keep the historical ``*args`` form. Functions present in the reference get an
explicit signature emitted into ``functions_generated.py`` instead.
"""
def wrapper(*args):
return _dispatch(fname, list(args))
wrapper.__doc__ = f"Auto-generated wrapper for {fname}"
wrapper.__name__ = fname
return wrapper
def _dict_to_options_column(options_dict):
"""Convert a Python dict[str, str] to a Column map literal.
This is used for avro/protobuf options which are dictionaries in the Python API
but need to be Column map literals in the Rust API.
"""
if options_dict is None:
return None
if isinstance(options_dict, dict):
# Convert dict to alternating keys and values: [k1, v1, k2, v2, ...]
args = []
for k, v in options_dict.items():
args.append(k)
args.append(v)
# Use create_map with literal strings - we'll build the Column through _call_function
# since create_map is auto-generated
return _call_function("create_map", *[_pyfunc_lit(arg) for arg in args])
# If already a Column, pass through
return options_dict
# Import UDF / UDTF functions
from pyspark.sql.udf import udf, pandas_udf, arrow_udf
from pyspark.sql.udtf import (
udtf,
arrow_udtf,
OrderingColumn,
PartitioningColumn,
SelectedColumn,
)
# Hand-written special functions. Thin wrappers so the public parameter names match
# the reference PySpark 4.2.0 signatures (keyword-call parity), forwarding to the native
# bindings whose Rust arg names differ.
def col(col):
"""Returns a Column based on the given column name."""
return _pyfunc_col(col)
def lit(col):
"""Creates a Column of literal value."""
return _pyfunc_lit(col)
def expr(str):
"""Parses the expression string into the column that it represents."""
return _pyfunc_expr(str)
sum = _col_wrapper(_pyfunc_sum)
count = _col_wrapper(_pyfunc_count)
avg = _col_wrapper(_pyfunc_avg)
max = _col_wrapper(_pyfunc_max)
min = _col_wrapper(_pyfunc_min)
when = _pyfunc_when
# Mixed/special functions - explicit wrappers for non-generic dispatch
def sha2(col, numBits):
"""Returns the hex string result of SHA2 digest of the given data.
Args:
col: column to hash
numBits: either 256 or 512
"""
return _pyfunc_sha2(_to_col(col), numBits)
def window(timeColumn, windowDuration, slideDuration=None, startTime=None):
"""Buckets rows into one or more time windows specified by the given parameters.
Args:
timeColumn: the column containing timestamps
windowDuration: a string specifying the width of the window, e.g. '10 minutes'
slideDuration: optional, the slide interval. Defaults to ``windowDuration``
(a tumbling window) when only ``startTime`` is given.
startTime: optional, the offset of the first window, e.g. '15 minutes'.
Defaults to '0 second'.
"""
if slideDuration is None and startTime is None:
return _pyfunc_window(_to_col(timeColumn), windowDuration)
slide = slideDuration if slideDuration is not None else windowDuration
start = startTime if startTime is not None else "0 second"
return _pyfunc_window_with_slide_and_start(
_to_col(timeColumn), windowDuration, slide, start
)
def from_avro(data, json_format_schema):
"""Deserialize Avro data into a column.
Args:
data: column containing binary Avro data
json_format_schema: JSON string schema for the Avro data
"""
return _pyfunc_from_avro(_to_col(data), json_format_schema)
def from_avro_with_options(data, json_format_schema, options=None):
"""Deserialize Avro data into a column with options.
Args:
data: column containing binary Avro data
json_format_schema: JSON string schema for the Avro data
options: dict or Column of options
"""
if options is None:
return _pyfunc_from_avro(_to_col(data), json_format_schema)
options_col = _dict_to_options_column(options)
return _pyfunc_from_avro_with_options(_to_col(data), json_format_schema, options_col)
def to_avro_with_schema(data, json_format_schema):
"""Serialize a column to Avro binary format with schema.
Args:
data: column to serialize
json_format_schema: JSON string schema for the Avro data
"""
return _pyfunc_to_avro_with_schema(_to_col(data), json_format_schema)
def from_protobuf(data, message_name, desc_file_path=None, options=None, binary_descriptor_set=None):
"""Deserialize Protobuf data into a column.
Args:
data: column containing binary Protobuf data
message_name: name of the Protobuf message type
desc_file_path: optional, path to descriptor file
options: optional, dict or Column of options
binary_descriptor_set: optional, binary descriptor set bytes
"""
if binary_descriptor_set is not None and options is not None:
options_col = _dict_to_options_column(options)
return _pyfunc_from_protobuf_with_descriptor_and_options(
_to_col(data), message_name, binary_descriptor_set, options_col
)
elif binary_descriptor_set is not None:
return _pyfunc_from_protobuf_with_descriptor(
_to_col(data), message_name, binary_descriptor_set
)
elif options is not None:
options_col = _dict_to_options_column(options)
return _pyfunc_from_protobuf_with_options(_to_col(data), message_name, options_col)
else:
return _pyfunc_from_protobuf(_to_col(data), message_name)
def to_protobuf(data, message_name, desc_file_path=None, options=None, binary_descriptor_set=None):
"""Serialize a column to Protobuf binary format.
Args:
data: column to serialize
message_name: name of the Protobuf message type
desc_file_path: optional, path to descriptor file
options: optional, dict or Column of options
binary_descriptor_set: optional, binary descriptor set bytes
"""
if binary_descriptor_set is not None and options is not None:
options_col = _dict_to_options_column(options)
return _pyfunc_to_protobuf_with_descriptor_and_options(
_to_col(data), message_name, binary_descriptor_set, options_col
)
elif binary_descriptor_set is not None:
return _pyfunc_to_protobuf_with_descriptor(
_to_col(data), message_name, binary_descriptor_set
)
elif options is not None:
options_col = _dict_to_options_column(options)
return _pyfunc_to_protobuf_with_options(_to_col(data), message_name, options_col)
else:
return _pyfunc_to_protobuf(_to_col(data), message_name)
# Now we can import generated wrappers (they use _create_wrapper defined above)
# We do this via exec to make _create_wrapper available in the generated module's namespace
import importlib.util
spec = importlib.util.spec_from_file_location("functions_generated",
os.path.join(os.path.dirname(__file__), "functions_generated.py"))
_gen_module = importlib.util.module_from_spec(spec)
# Inject our helper functions into the generated module's namespace before loading
_gen_module._create_wrapper = _create_wrapper
_gen_module._dispatch = _dispatch
_gen_module._UNSET = _UNSET
_gen_module._wrap = _wrap
_gen_module._unwrap = _unwrap
_gen_module._to_col = _to_col
_gen_module._call_function = _call_function
spec.loader.exec_module(_gen_module)
# Import all generated functions into this module
for name in dir(_gen_module):
if not name.startswith('_'):
globals()[name] = getattr(_gen_module, name)
# Overrides for functions whose Python signature carries extra literal args that the
# generated single-arg wrapper cannot express (offset/default, variadic field names).
# Defined AFTER the generated import so they take precedence. Field/offset/default are
# literals; only the first arg is a column (PySpark ColumnOrName).
def lag(col, offset=1, default=None):
"""lag(col, offset=1, default=None): value offset rows before the current row."""
return _wrap(_call_function("lag", _to_col(col), _pyfunc_lit(offset), _pyfunc_lit(default)))
def lead(col, offset=1, default=None):
"""lead(col, offset=1, default=None): value offset rows after the current row."""
return _wrap(_call_function("lead", _to_col(col), _pyfunc_lit(offset), _pyfunc_lit(default)))
def json_tuple(col, *fields):
"""json_tuple(col, *fields): extract the given JSON fields (literal names) as columns."""
if not fields:
raise ValueError("json_tuple() takes at least one field name")
return _wrap(_call_function("json_tuple", _to_col(col), *[_pyfunc_lit(f) for f in fields]))
# ---------------------------------------------------------------------------
# Higher-order functions.
#
# Mirrors pyspark.sql.connect.functions._get_lambda_parameters / _create_lambda /
# _invoke_higher_order_function exactly: a monotonic counter yields fresh variable
# names (``x_0``, ``y_1`` ...), placeholder Columns wrap UnresolvedNamedLambdaVariable,
# the user's callable is invoked to build the body, and the resulting LambdaFunction
# is passed as an argument to an UnresolvedFunction call. Defined AFTER the generated
# import so they take precedence over any generic wrapper.
# ---------------------------------------------------------------------------
import inspect as _inspect
from pyspark.errors import PySparkValueError as _PySparkValueError
_named_lambda_variable = _functions.pyfunc_named_lambda_variable
_lambda_function = _functions.pyfunc_lambda_function
_call_named_function = _functions.pyfunc_call_named_function
# Generic UnresolvedFunction invoker for ANY name (not gated by the builtin
# dispatch allowlist). Mirrors pyspark.sql.connect.functions._invoke_function.
_invoke_function = _functions.pyfunc_invoke_function
# Global monotonic counter, mirroring UnresolvedNamedLambdaVariable._nextVarNameId.
_lambda_var_name_id = [0]
def _fresh_lambda_var_name(name):
_id = _lambda_var_name_id[0]
_lambda_var_name_id[0] += 1
return f"{name}_{_id}"
def _get_lambda_parameters(f):
parameters = list(_inspect.signature(f).parameters.values())
supported_parameter_types = {
_inspect.Parameter.POSITIONAL_OR_KEYWORD,
_inspect.Parameter.POSITIONAL_ONLY,
}
if not (1 <= len(parameters) <= 3):
raise _PySparkValueError(
errorClass="WRONG_NUM_ARGS_FOR_HIGHER_ORDER_FUNCTION",
messageParameters={"func_name": f.__name__, "num_args": str(len(parameters))},
)
if not all(p.kind in supported_parameter_types for p in parameters):
raise _PySparkValueError(
errorClass="UNSUPPORTED_PARAM_TYPE_FOR_HIGHER_ORDER_FUNCTION",
messageParameters={"func_name": f.__name__},
)
return parameters
def _create_lambda(f):
parameters = _get_lambda_parameters(f)
arg_names = ["x", "y", "z"][: len(parameters)]
var_names = [_fresh_lambda_var_name(n) for n in arg_names]
arg_cols = [_named_lambda_variable(v) for v in var_names]
result = f(*arg_cols)
if not isinstance(result, _pyspark.Column):
raise _PySparkValueError(
errorClass="HIGHER_ORDER_FUNCTION_SHOULD_RETURN_COLUMN",
messageParameters={"func_name": f.__name__, "return_type": type(result).__name__},
)
return _lambda_function(result, var_names)
def _invoke_higher_order_function(name, cols, funs):
_cols = [_to_col(c) for c in cols]
_funs = [_create_lambda(f) for f in funs]
return _invoke_function(name, *_cols, *_funs)
def transform(col, f):
return _invoke_higher_order_function("transform", [col], [f])
def exists(col, f):
return _invoke_higher_order_function("exists", [col], [f])
def forall(col, f):
return _invoke_higher_order_function("forall", [col], [f])
def filter(col, f): # noqa: A001 (shadows builtin, as in official pyspark)
return _invoke_higher_order_function("filter", [col], [f])
def aggregate(col, initialValue, merge, finish=None):
if finish is not None:
return _invoke_higher_order_function("aggregate", [col, initialValue], [merge, finish])
return _invoke_higher_order_function("aggregate", [col, initialValue], [merge])
def reduce(col, initialValue, merge, finish=None):
if finish is not None:
return _invoke_higher_order_function("reduce", [col, initialValue], [merge, finish])
return _invoke_higher_order_function("reduce", [col, initialValue], [merge])
def zip_with(left, right, f):
return _invoke_higher_order_function("zip_with", [left, right], [f])
def transform_keys(col, f):
return _invoke_higher_order_function("transform_keys", [col], [f])
def transform_values(col, f):
return _invoke_higher_order_function("transform_values", [col], [f])
def map_filter(col, f):
return _invoke_higher_order_function("map_filter", [col], [f])
def map_zip_with(col1, col2, f):
return _invoke_higher_order_function("map_zip_with", [col1, col2], [f])
# Misc functions mirroring pyspark.sql.connect.functions.
def cume_dist():
return _invoke_function("cume_dist")
# ``random`` is an alias for ``rand`` (matches pyspark.sql.functions).
random = rand
# ``column`` is an alias of ``col`` (matches pyspark.sql.functions).
def column(col):
"""Returns a Column based on the given column name (alias of ``col``)."""
return _pyfunc_col(col)
def call_udf(udfName, *cols):
# Mirrors _invoke_function(udfName, *cols): an UnresolvedFunction call.
return _invoke_function(udfName, *[_to_col(c) for c in cols])
def call_function(funcName, *cols):
# Mirrors ConnectColumn(CallFunction(funcName, expressions)).
return _call_named_function(funcName, *[_to_col(c) for c in cols])
def broadcast(df):
from pyspark.errors import PySparkTypeError
if not isinstance(df, _pyspark.DataFrame):
raise PySparkTypeError(
errorClass="NOT_EXPECTED_TYPE",
messageParameters={
"expected_type": "DataFrame",
"arg_name": "df",
"arg_type": type(df).__name__,
},
)
return df.hint("broadcast")
# Expose the `partitioning` submodule (bucket/days/hours/months/years) at
# ``pyspark.sql.functions.partitioning``. Since this module is a plain module (not a
# package), register a synthetic submodule holding the flat partition-transform
# functions so both ``import pyspark.sql.functions.partitioning`` and
# ``F.partitioning.bucket`` work, mirroring reference pyspark.
import sys as _sys
import types as _types
partitioning = _types.ModuleType("pyspark.sql.functions.partitioning")
partitioning.__doc__ = "Partition transform functions (bucket/days/hours/months/years)."
for _pname in ("years", "months", "days", "hours", "bucket"):
_obj = globals().get(_pname)
if _obj is not None:
setattr(partitioning, _pname, _obj)
partitioning.__all__ = [
_n for _n in ("years", "months", "days", "hours", "bucket") if hasattr(partitioning, _n)
]
_sys.modules["pyspark.sql.functions.partitioning"] = partitioning
# Build __all__ with all function names
__all__ = [
"col",
"lit",
"expr",
"sum",
"count",
"avg",
"max",
"min",
"when",
"udf",
"pandas_udf",
]
# Add all other function names (dynamically)
import inspect
for name, obj in list(globals().items()):
if (callable(obj) and
not name.startswith('_') and
name not in __all__ and
name not in ['inspect', 'importlib', '_gen_module', 'spec', 'os']):
__all__.append(name)
# Expose the `builtin` submodule at ``pyspark.sql.functions.builtin``. Upstream v4.2.0 splits
# functions into a package whose ``builtin`` submodule holds the actual functions and whose
# ``__init__`` re-exports them; here this module IS the implementation, so register a synthetic
# ``builtin`` submodule mirroring it (so ``import pyspark.sql.functions.builtin`` and
# ``from pyspark.sql.functions.builtin import col`` work like reference pyspark).
builtin = _types.ModuleType("pyspark.sql.functions.builtin")
builtin.__doc__ = "Built-in DataFrame functions (re-exported from pyspark.sql.functions)."
for _bname in list(__all__):
_bobj = globals().get(_bname)
if _bobj is not None:
setattr(builtin, _bname, _bobj)
builtin.__all__ = list(__all__)
_sys.modules["pyspark.sql.functions.builtin"] = builtin