Path: blob/main/crates/polars-expr/src/dispatch/strings.rs
7884 views
use std::borrow::Cow;1use std::sync::Arc;23use polars_core::prelude::*;4use polars_core::utils::{CustomIterTools, handle_casting_failures};5use polars_ops::prelude::{BinaryNameSpaceImpl, StringNameSpaceImpl};6#[cfg(feature = "temporal")]7use polars_plan::dsl::StrptimeOptions;8use polars_plan::dsl::{ColumnsUdf, SpecialEq};9use polars_plan::plans::IRStringFunction;10use polars_time::prelude::StringMethods;11#[cfg(feature = "regex")]12use regex::{NoExpand, escape};1314use super::*;1516pub fn function_expr_to_udf(func: IRStringFunction) -> SpecialEq<Arc<dyn ColumnsUdf>> {17use IRStringFunction::*;18match func {19Format { format, insertions } => {20map_as_slice!(strings::format, format.as_str(), insertions.as_ref())21},22#[cfg(feature = "regex")]23Contains { literal, strict } => map_as_slice!(strings::contains, literal, strict),24CountMatches(literal) => {25map_as_slice!(strings::count_matches, literal)26},27EndsWith => map_as_slice!(strings::ends_with),28StartsWith => map_as_slice!(strings::starts_with),29Extract(group_index) => map_as_slice!(strings::extract, group_index),30ExtractAll => {31map_as_slice!(strings::extract_all)32},33#[cfg(feature = "extract_groups")]34ExtractGroups { pat, dtype } => {35map!(strings::extract_groups, &pat, &dtype)36},37#[cfg(feature = "regex")]38Find { literal, strict } => map_as_slice!(strings::find, literal, strict),39LenBytes => map!(strings::len_bytes),40LenChars => map!(strings::len_chars),41#[cfg(feature = "string_pad")]42PadEnd { fill_char } => {43map_as_slice!(strings::pad_end, fill_char)44},45#[cfg(feature = "string_pad")]46PadStart { fill_char } => {47map_as_slice!(strings::pad_start, fill_char)48},49#[cfg(feature = "string_pad")]50ZFill => {51map_as_slice!(strings::zfill)52},53#[cfg(feature = "temporal")]54Strptime(dtype, options) => {55map_as_slice!(strings::strptime, dtype.clone(), &options)56},57Split(inclusive) => {58map_as_slice!(strings::split, inclusive)59},60#[cfg(feature = "dtype-struct")]61SplitExact { n, inclusive } => map_as_slice!(strings::split_exact, n, inclusive),62#[cfg(feature = "dtype-struct")]63SplitN(n) => map_as_slice!(strings::splitn, n),64#[cfg(feature = "concat_str")]65ConcatVertical {66delimiter,67ignore_nulls,68} => map!(strings::join, &delimiter, ignore_nulls),69#[cfg(feature = "concat_str")]70ConcatHorizontal {71delimiter,72ignore_nulls,73} => map_as_slice!(strings::concat_hor, &delimiter, ignore_nulls),74#[cfg(feature = "regex")]75Replace { n, literal } => map_as_slice!(strings::replace, literal, n),76#[cfg(feature = "string_normalize")]77Normalize { form } => map!(strings::normalize, form.clone()),78#[cfg(feature = "string_reverse")]79Reverse => map!(strings::reverse),80Uppercase => map!(uppercase),81Lowercase => map!(lowercase),82#[cfg(feature = "nightly")]83Titlecase => map!(strings::titlecase),84StripChars => map_as_slice!(strings::strip_chars),85StripCharsStart => map_as_slice!(strings::strip_chars_start),86StripCharsEnd => map_as_slice!(strings::strip_chars_end),87StripPrefix => map_as_slice!(strings::strip_prefix),88StripSuffix => map_as_slice!(strings::strip_suffix),89#[cfg(feature = "string_to_integer")]90ToInteger { dtype, strict } => {91map_as_slice!(strings::to_integer, dtype.clone(), strict)92},93Slice => map_as_slice!(strings::str_slice),94Head => map_as_slice!(strings::str_head),95Tail => map_as_slice!(strings::str_tail),96#[cfg(feature = "string_encoding")]97HexEncode => map!(strings::hex_encode),98#[cfg(feature = "binary_encoding")]99HexDecode(strict) => map!(strings::hex_decode, strict),100#[cfg(feature = "string_encoding")]101Base64Encode => map!(strings::base64_encode),102#[cfg(feature = "binary_encoding")]103Base64Decode(strict) => map!(strings::base64_decode, strict),104#[cfg(feature = "dtype-decimal")]105ToDecimal { scale } => map!(strings::to_decimal, scale),106#[cfg(feature = "extract_jsonpath")]107JsonDecode(dtype) => map!(strings::json_decode, dtype.clone()),108#[cfg(feature = "extract_jsonpath")]109JsonPathMatch => map_as_slice!(strings::json_path_match),110#[cfg(feature = "find_many")]111ContainsAny {112ascii_case_insensitive,113} => {114map_as_slice!(contains_any, ascii_case_insensitive)115},116#[cfg(feature = "find_many")]117ReplaceMany {118ascii_case_insensitive,119leftmost,120} => {121map_as_slice!(replace_many, ascii_case_insensitive, leftmost)122},123#[cfg(feature = "find_many")]124ExtractMany {125ascii_case_insensitive,126overlapping,127leftmost,128} => {129map_as_slice!(extract_many, ascii_case_insensitive, overlapping, leftmost)130},131#[cfg(feature = "find_many")]132FindMany {133ascii_case_insensitive,134overlapping,135leftmost,136} => {137map_as_slice!(find_many, ascii_case_insensitive, overlapping, leftmost)138},139#[cfg(feature = "regex")]140EscapeRegex => map!(escape_regex),141}142}143144#[cfg(feature = "find_many")]145fn contains_any(s: &[Column], ascii_case_insensitive: bool) -> PolarsResult<Column> {146let ca = s[0].str()?;147let patterns = s[1].list()?;148polars_ops::chunked_array::strings::contains_any(ca, patterns, ascii_case_insensitive)149.map(|out| out.into_column())150}151152#[cfg(feature = "find_many")]153fn replace_many(154s: &[Column],155ascii_case_insensitive: bool,156leftmost: bool,157) -> PolarsResult<Column> {158let ca = s[0].str()?;159let patterns = s[1].list()?;160let replace_with = s[2].list()?;161polars_ops::chunked_array::strings::replace_all(162ca,163patterns,164replace_with,165ascii_case_insensitive,166leftmost,167)168.map(|out| out.into_column())169}170171#[cfg(feature = "find_many")]172fn extract_many(173s: &[Column],174ascii_case_insensitive: bool,175overlapping: bool,176leftmost: bool,177) -> PolarsResult<Column> {178let ca = s[0].str()?;179let patterns = s[1].list()?;180181polars_ops::chunked_array::strings::extract_many(182ca,183patterns,184ascii_case_insensitive,185overlapping,186leftmost,187)188.map(|out| out.into_column())189}190191#[cfg(feature = "find_many")]192fn find_many(193s: &[Column],194ascii_case_insensitive: bool,195overlapping: bool,196leftmost: bool,197) -> PolarsResult<Column> {198let ca = s[0].str()?;199let patterns = s[1].list()?;200201polars_ops::chunked_array::strings::find_many(202ca,203patterns,204ascii_case_insensitive,205overlapping,206leftmost,207)208.map(|out| out.into_column())209}210211fn uppercase(s: &Column) -> PolarsResult<Column> {212let ca = s.str()?;213Ok(ca.to_uppercase().into_column())214}215216fn lowercase(s: &Column) -> PolarsResult<Column> {217let ca = s.str()?;218Ok(ca.to_lowercase().into_column())219}220221#[cfg(feature = "nightly")]222pub(super) fn titlecase(s: &Column) -> PolarsResult<Column> {223let ca = s.str()?;224Ok(ca.to_titlecase().into_column())225}226227pub(super) fn len_chars(s: &Column) -> PolarsResult<Column> {228let ca = s.str()?;229Ok(ca.str_len_chars().into_column())230}231232pub(super) fn len_bytes(s: &Column) -> PolarsResult<Column> {233let ca = s.str()?;234Ok(ca.str_len_bytes().into_column())235}236237#[cfg(feature = "regex")]238pub(super) fn contains(s: &[Column], literal: bool, strict: bool) -> PolarsResult<Column> {239_check_same_length(s, "contains")?;240let ca = s[0].str()?;241let pat = s[1].str()?;242ca.contains_chunked(pat, literal, strict)243.map(|ok| ok.into_column())244}245246#[cfg(feature = "regex")]247pub(super) fn find(s: &[Column], literal: bool, strict: bool) -> PolarsResult<Column> {248_check_same_length(s, "find")?;249let ca = s[0].str()?;250let pat = s[1].str()?;251ca.find_chunked(pat, literal, strict)252.map(|ok| ok.into_column())253}254255pub(super) fn ends_with(s: &[Column]) -> PolarsResult<Column> {256_check_same_length(s, "ends_with")?;257let ca = s[0].str()?.as_binary();258let suffix = s[1].str()?.as_binary();259260Ok(ca.ends_with_chunked(&suffix)?.into_column())261}262263pub(super) fn starts_with(s: &[Column]) -> PolarsResult<Column> {264_check_same_length(s, "starts_with")?;265let ca = s[0].str()?.as_binary();266let prefix = s[1].str()?.as_binary();267Ok(ca.starts_with_chunked(&prefix)?.into_column())268}269270/// Extract a regex pattern from the a string value.271pub(super) fn extract(s: &[Column], group_index: usize) -> PolarsResult<Column> {272let ca = s[0].str()?;273let pat = s[1].str()?;274ca.extract(pat, group_index).map(|ca| ca.into_column())275}276277#[cfg(feature = "extract_groups")]278/// Extract all capture groups from a regex pattern as a struct279pub(super) fn extract_groups(s: &Column, pat: &str, dtype: &DataType) -> PolarsResult<Column> {280let ca = s.str()?;281ca.extract_groups(pat, dtype).map(Column::from)282}283284#[cfg(feature = "string_pad")]285pub(super) fn pad_start(s: &[Column], fill_char: char) -> PolarsResult<Column> {286let s1 = s[0].as_materialized_series();287let length = &s[1];288polars_ensure!(289s1.len() == 1 || length.len() == 1 || s1.len() == length.len(),290ShapeMismatch: "cannot pad_start with 'length' array of length {}", length.len()291);292let length = length.as_materialized_series().u64()?;293let ca = s1.str()?;294Ok(ca.pad_start(length, fill_char).into_column())295}296297#[cfg(feature = "string_pad")]298pub(super) fn pad_end(s: &[Column], fill_char: char) -> PolarsResult<Column> {299let s1 = s[0].as_materialized_series();300let length = &s[1];301polars_ensure!(302s1.len() == 1 || length.len() == 1 || s1.len() == length.len(),303ShapeMismatch: "cannot pad_end with 'length' array of length {}", length.len()304);305let length = length.as_materialized_series().u64()?;306let ca = s1.str()?;307Ok(ca.pad_end(length, fill_char).into_column())308}309310#[cfg(feature = "string_pad")]311pub(super) fn zfill(s: &[Column]) -> PolarsResult<Column> {312let s1 = s[0].as_materialized_series();313let length = &s[1];314polars_ensure!(315s1.len() == 1 || length.len() == 1 || s1.len() == length.len(),316ShapeMismatch: "cannot zfill with 'length' array of length {}", length.len()317);318let length = length.as_materialized_series().u64()?;319let ca = s1.str()?;320Ok(ca.zfill(length).into_column())321}322323pub(super) fn strip_chars(s: &[Column]) -> PolarsResult<Column> {324_check_same_length(s, "strip_chars")?;325let ca = s[0].str()?;326let pat_s = &s[1];327ca.strip_chars(pat_s).map(|ok| ok.into_column())328}329330pub(super) fn strip_chars_start(s: &[Column]) -> PolarsResult<Column> {331_check_same_length(s, "strip_chars_start")?;332let ca = s[0].str()?;333let pat_s = &s[1];334ca.strip_chars_start(pat_s).map(|ok| ok.into_column())335}336337pub(super) fn strip_chars_end(s: &[Column]) -> PolarsResult<Column> {338_check_same_length(s, "strip_chars_end")?;339let ca = s[0].str()?;340let pat_s = &s[1];341ca.strip_chars_end(pat_s).map(|ok| ok.into_column())342}343344pub(super) fn strip_prefix(s: &[Column]) -> PolarsResult<Column> {345_check_same_length(s, "strip_prefix")?;346let ca = s[0].str()?;347let prefix = s[1].str()?;348Ok(ca.strip_prefix(prefix).into_column())349}350351pub(super) fn strip_suffix(s: &[Column]) -> PolarsResult<Column> {352_check_same_length(s, "strip_suffix")?;353let ca = s[0].str()?;354let suffix = s[1].str()?;355Ok(ca.strip_suffix(suffix).into_column())356}357358pub(super) fn extract_all(args: &[Column]) -> PolarsResult<Column> {359let s = &args[0];360let pat = &args[1];361362let ca = s.str()?;363let pat = pat.str()?;364365if pat.len() == 1 {366if let Some(pat) = pat.get(0) {367ca.extract_all(pat).map(|ca| ca.into_column())368} else {369Ok(Column::full_null(370ca.name().clone(),371ca.len(),372&DataType::List(Box::new(DataType::String)),373))374}375} else {376ca.extract_all_many(pat).map(|ca| ca.into_column())377}378}379380pub(super) fn count_matches(args: &[Column], literal: bool) -> PolarsResult<Column> {381let s = &args[0];382let pat = &args[1];383384let ca = s.str()?;385let pat = pat.str()?;386if pat.len() == 1 {387if let Some(pat) = pat.get(0) {388ca.count_matches(pat, literal).map(|ca| ca.into_column())389} else {390Ok(Column::full_null(391ca.name().clone(),392ca.len(),393&DataType::UInt32,394))395}396} else {397ca.count_matches_many(pat, literal)398.map(|ca| ca.into_column())399}400}401402#[cfg(feature = "temporal")]403pub(super) fn strptime(404s: &[Column],405dtype: DataType,406options: &StrptimeOptions,407) -> PolarsResult<Column> {408match dtype {409#[cfg(feature = "dtype-date")]410DataType::Date => to_date(&s[0], options),411#[cfg(feature = "dtype-datetime")]412DataType::Datetime(time_unit, time_zone) => {413to_datetime(s, &time_unit, time_zone.as_ref(), options)414},415#[cfg(feature = "dtype-time")]416DataType::Time => to_time(&s[0], options),417dt => polars_bail!(ComputeError: "not implemented for dtype {}", dt),418}419}420421#[cfg(feature = "dtype-struct")]422pub(super) fn split_exact(s: &[Column], n: usize, inclusive: bool) -> PolarsResult<Column> {423let ca = s[0].str()?;424let by = s[1].str()?;425426if inclusive {427ca.split_exact_inclusive(by, n).map(|ca| ca.into_column())428} else {429ca.split_exact(by, n).map(|ca| ca.into_column())430}431}432433#[cfg(feature = "dtype-struct")]434pub(super) fn splitn(s: &[Column], n: usize) -> PolarsResult<Column> {435let ca = s[0].str()?;436let by = s[1].str()?;437438ca.splitn(by, n).map(|ca| ca.into_column())439}440441pub(super) fn split(s: &[Column], inclusive: bool) -> PolarsResult<Column> {442let ca = s[0].str()?;443let by = s[1].str()?;444445if inclusive {446Ok(ca.split_inclusive(by)?.into_column())447} else {448Ok(ca.split(by)?.into_column())449}450}451452#[cfg(feature = "dtype-date")]453fn to_date(s: &Column, options: &StrptimeOptions) -> PolarsResult<Column> {454let ca = s.str()?;455let out = {456if options.exact {457ca.as_date(options.format.as_deref(), options.cache)?458.into_column()459} else {460ca.as_date_not_exact(options.format.as_deref())?461.into_column()462}463};464465if options.strict && ca.null_count() != out.null_count() {466handle_casting_failures(s.as_materialized_series(), out.as_materialized_series())?;467}468Ok(out.into_column())469}470471#[cfg(feature = "dtype-datetime")]472fn to_datetime(473s: &[Column],474time_unit: &TimeUnit,475time_zone: Option<&TimeZone>,476options: &StrptimeOptions,477) -> PolarsResult<Column> {478let datetime_strings = &s[0].str()?;479let ambiguous = &s[1].str()?;480481polars_ensure!(482datetime_strings.len() == ambiguous.len()483|| datetime_strings.len() == 1484|| ambiguous.len() == 1,485length_mismatch = "str.strptime",486datetime_strings.len(),487ambiguous.len()488);489490let tz_aware = match &options.format {491#[cfg(all(feature = "regex", feature = "timezones"))]492Some(format) => polars_plan::plans::TZ_AWARE_RE.is_match(format),493_ => false,494};495496let out = if options.exact {497datetime_strings498.as_datetime(499options.format.as_deref(),500*time_unit,501options.cache,502tz_aware,503time_zone,504ambiguous,505)?506.into_column()507} else {508datetime_strings509.as_datetime_not_exact(510options.format.as_deref(),511*time_unit,512tz_aware,513time_zone,514ambiguous,515true,516)?517.into_column()518};519520if options.strict && datetime_strings.null_count() != out.null_count() {521handle_casting_failures(s[0].as_materialized_series(), out.as_materialized_series())?;522}523Ok(out.into_column())524}525526#[cfg(feature = "dtype-time")]527fn to_time(s: &Column, options: &StrptimeOptions) -> PolarsResult<Column> {528polars_ensure!(529options.exact, ComputeError: "non-exact not implemented for Time data type"530);531532let ca = s.str()?;533let out = ca534.as_time(options.format.as_deref(), options.cache)?535.into_column();536537if options.strict && ca.null_count() != out.null_count() {538handle_casting_failures(s.as_materialized_series(), out.as_materialized_series())?;539}540Ok(out.into_column())541}542543#[cfg(feature = "concat_str")]544pub(super) fn join(s: &Column, delimiter: &str, ignore_nulls: bool) -> PolarsResult<Column> {545let str_s = s.cast(&DataType::String)?;546let joined = polars_ops::chunked_array::str_join(str_s.str()?, delimiter, ignore_nulls);547Ok(joined.into_column())548}549550#[cfg(feature = "concat_str")]551pub(super) fn concat_hor(552series: &[Column],553delimiter: &str,554ignore_nulls: bool,555) -> PolarsResult<Column> {556let str_series: Vec<_> = series557.iter()558.map(|s| s.cast(&DataType::String))559.collect::<PolarsResult<_>>()?;560let cas: Vec<_> = str_series.iter().map(|s| s.str().unwrap()).collect();561Ok(polars_ops::chunked_array::hor_str_concat(&cas, delimiter, ignore_nulls)?.into_column())562}563564#[cfg(feature = "regex")]565fn get_pat(pat: &StringChunked) -> PolarsResult<&str> {566pat.get(0).ok_or_else(567|| polars_err!(ComputeError: "pattern cannot be 'null' in 'replace' expression"),568)569}570571// used only if feature="regex"572#[allow(dead_code)]573fn iter_and_replace<'a, F>(ca: &'a StringChunked, val: &'a StringChunked, f: F) -> StringChunked574where575F: Fn(&'a str, &'a str) -> Cow<'a, str>,576{577let mut out: StringChunked = ca578.into_iter()579.zip(val)580.map(|(opt_src, opt_val)| match (opt_src, opt_val) {581(Some(src), Some(val)) => Some(f(src, val)),582(Some(src), None) => Some(Cow::from(src)),583_ => None,584})585.collect_trusted();586587out.rename(ca.name().clone());588out589}590591#[cfg(feature = "regex")]592fn is_literal_pat(pat: &str) -> bool {593pat.chars().all(|c| !c.is_ascii_punctuation())594}595596#[cfg(feature = "regex")]597fn replace_n<'a>(598ca: &'a StringChunked,599pat: &'a StringChunked,600val: &'a StringChunked,601literal: bool,602n: usize,603) -> PolarsResult<StringChunked> {604match (pat.len(), val.len()) {605(1, 1) => {606let pat = get_pat(pat)?;607let Some(val) = val.get(0) else {608return Ok(ca.clone());609};610let literal = literal || is_literal_pat(pat);611612match literal {613true => ca.replace_literal(pat, val, n),614false => {615if n > 1 {616polars_bail!(ComputeError: "regex replacement with 'n > 1' not yet supported")617}618ca.replace(pat, val)619},620}621},622(1, len_val) => {623if n > 1 {624polars_bail!(ComputeError: "multivalue replacement with 'n > 1' not yet supported")625}626627if n == 0 {628return Ok(ca.clone());629};630631// from here on, we know that n == 1632let mut pat = get_pat(pat)?.to_string();633polars_ensure!(634len_val == ca.len(),635ComputeError:636"replacement value length ({}) does not match string column length ({})",637len_val, ca.len(),638);639let lit = is_literal_pat(&pat);640let literal_pat = literal || lit;641642if literal_pat {643pat = escape(&pat)644}645646let reg = polars_utils::regex_cache::compile_regex(&pat)?;647648let f = |s: &'a str, val: &'a str| {649if literal {650reg.replace(s, NoExpand(val))651} else {652reg.replace(s, val)653}654};655656Ok(iter_and_replace(ca, val, f))657},658_ => polars_bail!(659ComputeError: "dynamic pattern length in 'str.replace' expressions is not supported yet"660),661}662}663664#[cfg(feature = "regex")]665fn replace_all<'a>(666ca: &'a StringChunked,667pat: &'a StringChunked,668val: &'a StringChunked,669literal: bool,670) -> PolarsResult<StringChunked> {671match (pat.len(), val.len()) {672(1, 1) => {673let pat = get_pat(pat)?;674let val = val.get(0).ok_or_else(675|| polars_err!(ComputeError: "value cannot be 'null' in 'replace' expression"),676)?;677let literal = literal || is_literal_pat(pat);678679match literal {680true => ca.replace_literal_all(pat, val),681false => ca.replace_all(pat, val),682}683},684(1, len_val) => {685let mut pat = get_pat(pat)?.to_string();686polars_ensure!(687len_val == ca.len(),688ComputeError:689"replacement value length ({}) does not match string column length ({})",690len_val, ca.len(),691);692693let literal_pat = literal || is_literal_pat(&pat);694695if literal_pat {696pat = escape(&pat)697}698699let reg = polars_utils::regex_cache::compile_regex(&pat)?;700701let f = |s: &'a str, val: &'a str| {702// According to the docs for replace_all703// when literal = True then capture groups are ignored.704if literal {705reg.replace_all(s, NoExpand(val))706} else {707reg.replace_all(s, val)708}709};710711Ok(iter_and_replace(ca, val, f))712},713_ => polars_bail!(714ComputeError: "dynamic pattern length in 'str.replace' expressions is not supported yet"715),716}717}718719pub(super) fn format(s: &mut [Column], format: &str, insertions: &[usize]) -> PolarsResult<Column> {720polars_ops::series::str_format(s, format, insertions)721}722723#[cfg(feature = "regex")]724pub(super) fn replace(s: &[Column], literal: bool, n: i64) -> PolarsResult<Column> {725let column = &s[0];726let pat = &s[1];727let val = &s[2];728let all = n < 0;729730let column = column.str()?;731let pat = pat.str()?;732let val = val.str()?;733734if all {735replace_all(column, pat, val, literal)736} else {737replace_n(column, pat, val, literal, n as usize)738}739.map(|ca| ca.into_column())740}741742#[cfg(feature = "string_normalize")]743pub(super) fn normalize(744s: &Column,745form: polars_ops::prelude::UnicodeForm,746) -> PolarsResult<Column> {747let ca = s.str()?;748Ok(ca.str_normalize(form).into_column())749}750751#[cfg(feature = "string_reverse")]752pub(super) fn reverse(s: &Column) -> PolarsResult<Column> {753let ca = s.str()?;754Ok(ca.str_reverse().into_column())755}756757#[cfg(feature = "string_to_integer")]758pub(super) fn to_integer(759s: &[Column],760dtype: Option<DataType>,761strict: bool,762) -> PolarsResult<Column> {763let ca = s[0].str()?;764let base = s[1].strict_cast(&DataType::UInt32)?;765ca.to_integer(base.u32()?, dtype, strict)766.map(|ok| ok.into_column())767}768769fn _ensure_lengths(s: &[Column]) -> bool {770// Calculate the post-broadcast length and ensure everything is consistent.771let len = s772.iter()773.map(|series| series.len())774.filter(|l| *l != 1)775.max()776.unwrap_or(1);777s.iter()778.all(|series| series.len() == 1 || series.len() == len)779}780781fn _check_same_length(s: &[Column], fn_name: &str) -> Result<(), PolarsError> {782polars_ensure!(783_ensure_lengths(s),784ShapeMismatch: "all series in `str.{}()` should have equal or unit length",785fn_name786);787Ok(())788}789790pub(super) fn str_slice(s: &[Column]) -> PolarsResult<Column> {791_check_same_length(s, "slice")?;792let ca = s[0].str()?;793let offset = &s[1];794let length = &s[2];795Ok(ca.str_slice(offset, length)?.into_column())796}797798pub(super) fn str_head(s: &[Column]) -> PolarsResult<Column> {799_check_same_length(s, "head")?;800let ca = s[0].str()?;801let n = &s[1];802Ok(ca.str_head(n)?.into_column())803}804805pub(super) fn str_tail(s: &[Column]) -> PolarsResult<Column> {806_check_same_length(s, "tail")?;807let ca = s[0].str()?;808let n = &s[1];809Ok(ca.str_tail(n)?.into_column())810}811812#[cfg(feature = "string_encoding")]813pub(super) fn hex_encode(s: &Column) -> PolarsResult<Column> {814Ok(s.str()?.hex_encode().into_column())815}816817#[cfg(feature = "binary_encoding")]818pub(super) fn hex_decode(s: &Column, strict: bool) -> PolarsResult<Column> {819s.str()?.hex_decode(strict).map(|ca| ca.into_column())820}821822#[cfg(feature = "string_encoding")]823pub(super) fn base64_encode(s: &Column) -> PolarsResult<Column> {824Ok(s.str()?.base64_encode().into_column())825}826827#[cfg(feature = "binary_encoding")]828pub(super) fn base64_decode(s: &Column, strict: bool) -> PolarsResult<Column> {829s.str()?.base64_decode(strict).map(|ca| ca.into_column())830}831832#[cfg(feature = "dtype-decimal")]833pub(super) fn to_decimal(s: &Column, scale: usize) -> PolarsResult<Column> {834let ca = s.str()?;835ca.to_decimal(polars_compute::decimal::DEC128_MAX_PREC, scale)836.map(Column::from)837}838839#[cfg(feature = "extract_jsonpath")]840pub(super) fn json_decode(s: &Column, dtype: DataType) -> PolarsResult<Column> {841use polars_ops::prelude::Utf8JsonPathImpl;842843let ca = s.str()?;844ca.json_decode(Some(dtype), None).map(Column::from)845}846847#[cfg(feature = "extract_jsonpath")]848pub(super) fn json_path_match(s: &[Column]) -> PolarsResult<Column> {849use polars_ops::prelude::Utf8JsonPathImpl;850851_check_same_length(s, "json_path_match")?;852let ca = s[0].str()?;853let pat = s[1].str()?;854Ok(ca.json_path_match(pat)?.into_column())855}856857#[cfg(feature = "regex")]858pub(super) fn escape_regex(s: &Column) -> PolarsResult<Column> {859let ca = s.str()?;860Ok(ca.str_escape_regex().into_column())861}862863864