1use polars_error::feature_gated;
4
5use crate::prelude::*;
6use crate::series::arithmetic::coerce_lhs_rhs;
7#[cfg(feature = "dtype-decimal")]
8use crate::series::arithmetic::decimal_op_operands;
9use crate::series::nulls::replace_non_null;
10
11macro_rules! impl_eq_compare {
12 ($self:expr, $rhs:expr, $method:ident) => {{
13 use DataType::*;
14 let (lhs, rhs) = ($self, $rhs);
15 validate_types(lhs.dtype(), rhs.dtype())?;
16
17 polars_ensure!(
18 lhs.len() == rhs.len() ||
19
20 lhs.len() == 1 ||
22 rhs.len() == 1,
23 ShapeMismatch: "could not compare between two series of different length ({} != {})",
24 lhs.len(),
25 rhs.len()
26 );
27
28 match (lhs.dtype(), rhs.dtype()) {
29 #[cfg(feature = "dtype-categorical")]
30 (Categorical(lcats, _), Categorical(rcats, _)) => {
31 ensure_same_categories(lcats, rcats)?;
32 return with_match_categorical_physical_type!(lcats.physical(), |$C| {
33 lhs.cat::<$C>().unwrap().$method(rhs.cat::<$C>().unwrap())
34 })
35 },
36 #[cfg(feature = "dtype-categorical")]
37 (Enum(lfcats, _), Enum(rfcats, _)) => {
38 ensure_same_frozen_categories(lfcats, rfcats)?;
39 return with_match_categorical_physical_type!(lfcats.physical(), |$C| {
40 lhs.cat::<$C>().unwrap().$method(rhs.cat::<$C>().unwrap())
41 })
42 },
43 #[cfg(feature = "dtype-categorical")]
44 (Categorical(_, _) | Enum(_, _), String) => {
45 return with_match_categorical_physical_type!(lhs.dtype().cat_physical().unwrap(), |$C| {
46 Ok(lhs.cat::<$C>().unwrap().$method(rhs.str().unwrap()))
47 })
48 },
49 #[cfg(feature = "dtype-categorical")]
50 (String, Categorical(_, _) | Enum(_, _)) => {
51 return with_match_categorical_physical_type!(rhs.dtype().cat_physical().unwrap(), |$C| {
52 Ok(rhs.cat::<$C>().unwrap().$method(lhs.str().unwrap()))
53 })
54 },
55 #[cfg(feature = "dtype-map")]
56 (ldt @ Map(_, _), rdt @ Map(_, _)) if ldt == rdt => {
57 let lhs = lhs.map().unwrap();
58 let rhs = rhs.map().unwrap();
59 return lhs.storage().$method(rhs.storage());
60 },
61
62 #[cfg(feature = "dtype-extension")]
63 (le @ Extension(_, _), re @ Extension(_, _)) if le == re => {
64 let lhs = lhs.ext().unwrap();
65 let rhs = rhs.ext().unwrap();
66 return lhs.storage().$method(rhs.storage());
67 },
68
69 #[cfg(feature = "dtype-extension")]
70 (Extension(_, storage), rdt) if **storage == *rdt => {
71 let lhs = lhs.ext().unwrap();
72 return lhs.storage().$method(rhs);
73 },
74
75 #[cfg(feature = "dtype-extension")]
76 (ldt, Extension(_, storage)) if *ldt == **storage => {
77 let rhs = rhs.ext().unwrap();
78 return lhs.$method(rhs.storage());
79 },
80 _ => (),
81 };
82
83 #[cfg(feature = "dtype-decimal")]
84 if let Some((l, r)) = decimal_cmp_operands(lhs, rhs)? {
85 let mut out = l.$method(&r);
86 out.rename(lhs.name().clone());
87 return Ok(out);
88 }
89
90 let (lhs, rhs) = coerce_lhs_rhs(lhs, rhs)
91 .map_err(|_| polars_err!(
92 SchemaMismatch: "could not evaluate comparison between series '{}' of dtype: {:?} and series '{}' of dtype: {:?}",
93 lhs.name(), lhs.dtype(), rhs.name(), rhs.dtype()
94 ))?;
95 let lhs = lhs.to_physical_repr();
96 let rhs = rhs.to_physical_repr();
97 let mut out = match lhs.dtype() {
98 Null => lhs.null().unwrap().$method(rhs.null().unwrap()),
99 Boolean => lhs.bool().unwrap().$method(rhs.bool().unwrap()),
100 String => lhs.str().unwrap().$method(rhs.str().unwrap()),
101 Binary => lhs.binary().unwrap().$method(rhs.binary().unwrap()),
102 BinaryOffset => lhs.binary_offset().unwrap().$method(rhs.binary_offset().unwrap()),
103 UInt8 => feature_gated!("dtype-u8", lhs.u8().unwrap().$method(rhs.u8().unwrap())),
104 UInt16 => feature_gated!("dtype-u16", lhs.u16().unwrap().$method(rhs.u16().unwrap())),
105 UInt32 => lhs.u32().unwrap().$method(rhs.u32().unwrap()),
106 UInt64 => lhs.u64().unwrap().$method(rhs.u64().unwrap()),
107 UInt128 => feature_gated!("dtype-u128", lhs.u128().unwrap().$method(rhs.u128().unwrap())),
108 Int8 => feature_gated!("dtype-i8", lhs.i8().unwrap().$method(rhs.i8().unwrap())),
109 Int16 => feature_gated!("dtype-i16", lhs.i16().unwrap().$method(rhs.i16().unwrap())),
110 Int32 => lhs.i32().unwrap().$method(rhs.i32().unwrap()),
111 Int64 => lhs.i64().unwrap().$method(rhs.i64().unwrap()),
112 Int128 => feature_gated!("dtype-i128", lhs.i128().unwrap().$method(rhs.i128().unwrap())),
113 Float16 => feature_gated!("dtype-f16", lhs.f16().unwrap().$method(rhs.f16().unwrap())),
114 Float32 => lhs.f32().unwrap().$method(rhs.f32().unwrap()),
115 Float64 => lhs.f64().unwrap().$method(rhs.f64().unwrap()),
116 List(_) => lhs.list().unwrap().$method(rhs.list().unwrap()),
117 #[cfg(feature = "dtype-array")]
118 Array(_, _) => lhs.array().unwrap().$method(rhs.array().unwrap()),
119 #[cfg(feature = "dtype-struct")]
120 Struct(_) => lhs.struct_().unwrap().$method(rhs.struct_().unwrap()),
121
122 dt => polars_bail!(InvalidOperation: "could not apply comparison on series of dtype '{}; operand names: '{}', '{}'", dt, lhs.name(), rhs.name()),
123 };
124 out.rename(lhs.name().clone());
125 PolarsResult::Ok(out)
126 }};
127}
128
129macro_rules! bail_invalid_ineq {
130 ($lhs:expr, $rhs:expr, $op:literal) => {
131 polars_bail!(
132 InvalidOperation: "cannot perform '{}' comparison between series '{}' of dtype: {} and series '{}' of dtype: {}",
133 $op,
134 $lhs.name(), $lhs.dtype(),
135 $rhs.name(), $rhs.dtype(),
136 )
137 };
138}
139
140macro_rules! impl_ineq_compare {
141 ($self:expr, $rhs:expr, $method:ident, $op:literal, $rev_method:ident) => {{
142 use DataType::*;
143 let (lhs, rhs) = ($self, $rhs);
144 validate_types(lhs.dtype(), rhs.dtype())?;
145
146 polars_ensure!(
147 lhs.len() == rhs.len() ||
148
149 lhs.len() == 1 ||
151 rhs.len() == 1,
152 ShapeMismatch:
153 "could not perform '{}' comparison between series '{}' of length: {} and series '{}' of length: {}, because they have different lengths",
154 $op,
155 lhs.name(), lhs.len(),
156 rhs.name(), rhs.len()
157 );
158
159 match (lhs.dtype(), rhs.dtype()) {
160 #[cfg(feature = "dtype-categorical")]
161 (Categorical(lcats, _), Categorical(rcats, _)) => {
162 ensure_same_categories(lcats, rcats)?;
163 return with_match_categorical_physical_type!(lcats.physical(), |$C| {
164 lhs.cat::<$C>().unwrap().$method(rhs.cat::<$C>().unwrap())
165 })
166 },
167 #[cfg(feature = "dtype-categorical")]
168 (Enum(lfcats, _), Enum(rfcats, _)) => {
169 ensure_same_frozen_categories(lfcats, rfcats)?;
170 return with_match_categorical_physical_type!(lfcats.physical(), |$C| {
171 lhs.cat::<$C>().unwrap().$method(rhs.cat::<$C>().unwrap())
172 })
173 },
174 #[cfg(feature = "dtype-categorical")]
175 (Categorical(_, _) | Enum(_, _), String) => {
176 return with_match_categorical_physical_type!(lhs.dtype().cat_physical().unwrap(), |$C| {
177 lhs.cat::<$C>().unwrap().$method(rhs.str().unwrap())
178 })
179 },
180 #[cfg(feature = "dtype-categorical")]
181 (String, Categorical(_, _) | Enum(_, _)) => {
182 return with_match_categorical_physical_type!(rhs.dtype().cat_physical().unwrap(), |$C| {
183 rhs.cat::<$C>().unwrap().$rev_method(lhs.str().unwrap())
185 })
186 },
187 #[cfg(feature = "dtype-map")]
189 (Map(_, _), _) | (_, Map(_, _)) => bail_invalid_ineq!(lhs, rhs, $op),
190
191 #[cfg(feature = "dtype-extension")]
192 (le @ Extension(_, _), re @ Extension(_, _)) if le == re => {
193 let lhs = lhs.ext().unwrap();
194 let rhs = rhs.ext().unwrap();
195 return lhs.storage().$method(rhs.storage());
196 },
197
198 #[cfg(feature = "dtype-extension")]
199 (Extension(_, storage), rdt) if **storage == *rdt => {
200 let lhs = lhs.ext().unwrap();
201 return lhs.storage().$method(rhs);
202 },
203
204 #[cfg(feature = "dtype-extension")]
205 (ldt, Extension(_, storage)) if *ldt == **storage => {
206 let rhs = rhs.ext().unwrap();
207 return lhs.$method(rhs.storage());
208 },
209 _ => (),
210 };
211
212 #[cfg(feature = "dtype-decimal")]
213 if let Some((l, r)) = decimal_cmp_operands(lhs, rhs)? {
214 let mut out = l.$method(&r);
215 out.rename(lhs.name().clone());
216 return Ok(out);
217 }
218
219 let (lhs, rhs) = coerce_lhs_rhs(lhs, rhs).map_err(|_|
220 polars_err!(
221 SchemaMismatch: "could not evaluate '{}' comparison between series '{}' of dtype: {:?} and series '{}' of dtype: {:?}",
222 $op,
223 lhs.name(), lhs.dtype(),
224 rhs.name(), rhs.dtype()
225 )
226 )?;
227 let lhs = lhs.to_physical_repr();
228 let rhs = rhs.to_physical_repr();
229 let mut out = match lhs.dtype() {
230 Null => lhs.null().unwrap().$method(rhs.null().unwrap()),
231 Boolean => lhs.bool().unwrap().$method(rhs.bool().unwrap()),
232 String => lhs.str().unwrap().$method(rhs.str().unwrap()),
233 Binary => lhs.binary().unwrap().$method(rhs.binary().unwrap()),
234 BinaryOffset => lhs.binary_offset().unwrap().$method(rhs.binary_offset().unwrap()),
235 UInt8 => feature_gated!("dtype-u8", lhs.u8().unwrap().$method(rhs.u8().unwrap())),
236 UInt16 => feature_gated!("dtype-u16", lhs.u16().unwrap().$method(rhs.u16().unwrap())),
237 UInt32 => lhs.u32().unwrap().$method(rhs.u32().unwrap()),
238 UInt64 => lhs.u64().unwrap().$method(rhs.u64().unwrap()),
239 UInt128 => feature_gated!("dtype-u128", lhs.u128().unwrap().$method(rhs.u128().unwrap())),
240 Int8 => feature_gated!("dtype-i8", lhs.i8().unwrap().$method(rhs.i8().unwrap())),
241 Int16 => feature_gated!("dtype-i16", lhs.i16().unwrap().$method(rhs.i16().unwrap())),
242 Int32 => lhs.i32().unwrap().$method(rhs.i32().unwrap()),
243 Int64 => lhs.i64().unwrap().$method(rhs.i64().unwrap()),
244 Int128 => feature_gated!("dtype-i128", lhs.i128().unwrap().$method(rhs.i128().unwrap())),
245 Float16 => feature_gated!("dtype-f16", lhs.f16().unwrap().$method(rhs.f16().unwrap())),
246 Float32 => lhs.f32().unwrap().$method(rhs.f32().unwrap()),
247 Float64 => lhs.f64().unwrap().$method(rhs.f64().unwrap()),
248 List(_) => bail_invalid_ineq!(lhs, rhs, $op),
249 #[cfg(feature = "dtype-array")]
250 Array(_, _) => bail_invalid_ineq!(lhs, rhs, $op),
251 #[cfg(feature = "dtype-struct")]
252 Struct(_) => bail_invalid_ineq!(lhs, rhs, $op),
253
254 dt => polars_bail!(InvalidOperation: "could not apply comparison on series of dtype '{}'; operand names: '{}', '{}'", dt, lhs.name(), rhs.name()),
255 };
256 out.rename(lhs.name().clone());
257 PolarsResult::Ok(out)
258 }};
259}
260
261#[cfg(feature = "dtype-decimal")]
263fn decimal_cmp_operands(
264 lhs: &Series,
265 rhs: &Series,
266) -> PolarsResult<Option<(Int128Chunked, Int128Chunked)>> {
267 use polars_compute::decimal::dec128_upscale_saturating;
268
269 let Some(operands) = decimal_op_operands(lhs, rhs) else {
270 return Ok(None);
271 };
272 let (lhs, rhs) = operands?;
273 let (lhs, rhs) = (lhs.decimal()?, rhs.decimal()?);
274 let scale = lhs.scale().max(rhs.scale());
275 let upscale = |ca: &DecimalChunked| {
276 let e = scale - ca.scale();
277 if e == 0 {
278 ca.physical().clone()
279 } else {
280 ca.physical()
281 .apply_values(|v| dec128_upscale_saturating(v, e))
282 }
283 };
284 Ok(Some((upscale(lhs), upscale(rhs))))
285}
286
287fn validate_types(left: &DataType, right: &DataType) -> PolarsResult<()> {
288 use DataType::*;
289
290 match (left, right) {
291 (String, dt) | (dt, String) if dt.is_primitive_numeric() => {
292 polars_bail!(ComputeError: "cannot compare string with numeric type ({})", dt)
293 },
294 #[cfg(feature = "dtype-categorical")]
295 (Categorical(_, _) | Enum(_, _), dt) | (dt, Categorical(_, _) | Enum(_, _))
296 if !(dt.is_categorical() | dt.is_string() | dt.is_enum()) =>
297 {
298 polars_bail!(ComputeError: "cannot compare categorical with {}", dt)
299 },
300 #[cfg(feature = "dtype-duration")]
301 (Date, Duration(_)) | (Duration(_), Date) => {
302 polars_bail!(ComputeError: "cannot compare date with duration")
303 },
304 _ => (),
305 };
306 Ok(())
307}
308
309impl ChunkCompareEq<&Series> for Series {
310 type Item = PolarsResult<BooleanChunked>;
311
312 fn equal(&self, rhs: &Series) -> Self::Item {
314 impl_eq_compare!(self, rhs, equal)
315 }
316
317 fn equal_missing(&self, rhs: &Series) -> Self::Item {
319 impl_eq_compare!(self, rhs, equal_missing)
320 }
321
322 fn not_equal(&self, rhs: &Series) -> Self::Item {
324 impl_eq_compare!(self, rhs, not_equal)
325 }
326
327 fn not_equal_missing(&self, rhs: &Series) -> Self::Item {
329 impl_eq_compare!(self, rhs, not_equal_missing)
330 }
331}
332
333impl ChunkCompareIneq<&Series> for Series {
334 type Item = PolarsResult<BooleanChunked>;
335
336 fn gt(&self, rhs: &Series) -> Self::Item {
338 impl_ineq_compare!(self, rhs, gt, ">", lt)
339 }
340
341 fn gt_eq(&self, rhs: &Series) -> Self::Item {
343 impl_ineq_compare!(self, rhs, gt_eq, ">=", lt_eq)
344 }
345
346 fn lt(&self, rhs: &Series) -> Self::Item {
348 impl_ineq_compare!(self, rhs, lt, "<", gt)
349 }
350
351 fn lt_eq(&self, rhs: &Series) -> Self::Item {
353 impl_ineq_compare!(self, rhs, lt_eq, "<=", gt_eq)
354 }
355}
356
357impl<Rhs> ChunkCompareEq<Rhs> for Series
358where
359 Rhs: NumericNative,
360{
361 type Item = PolarsResult<BooleanChunked>;
362
363 fn equal(&self, rhs: Rhs) -> Self::Item {
364 validate_types(self.dtype(), &DataType::Int8)?;
365 let s = self.to_physical_repr();
366 Ok(apply_method_physical_numeric!(&s, equal, rhs))
367 }
368
369 fn equal_missing(&self, rhs: Rhs) -> Self::Item {
370 validate_types(self.dtype(), &DataType::Int8)?;
371 let s = self.to_physical_repr();
372 Ok(apply_method_physical_numeric!(&s, equal_missing, rhs))
373 }
374
375 fn not_equal(&self, rhs: Rhs) -> Self::Item {
376 validate_types(self.dtype(), &DataType::Int8)?;
377 let s = self.to_physical_repr();
378 Ok(apply_method_physical_numeric!(&s, not_equal, rhs))
379 }
380
381 fn not_equal_missing(&self, rhs: Rhs) -> Self::Item {
382 validate_types(self.dtype(), &DataType::Int8)?;
383 let s = self.to_physical_repr();
384 Ok(apply_method_physical_numeric!(&s, not_equal_missing, rhs))
385 }
386}
387
388impl<Rhs> ChunkCompareIneq<Rhs> for Series
389where
390 Rhs: NumericNative,
391{
392 type Item = PolarsResult<BooleanChunked>;
393
394 fn gt(&self, rhs: Rhs) -> Self::Item {
395 validate_types(self.dtype(), &DataType::Int8)?;
396 let s = self.to_physical_repr();
397 Ok(apply_method_physical_numeric!(&s, gt, rhs))
398 }
399
400 fn gt_eq(&self, rhs: Rhs) -> Self::Item {
401 validate_types(self.dtype(), &DataType::Int8)?;
402 let s = self.to_physical_repr();
403 Ok(apply_method_physical_numeric!(&s, gt_eq, rhs))
404 }
405
406 fn lt(&self, rhs: Rhs) -> Self::Item {
407 validate_types(self.dtype(), &DataType::Int8)?;
408 let s = self.to_physical_repr();
409 Ok(apply_method_physical_numeric!(&s, lt, rhs))
410 }
411
412 fn lt_eq(&self, rhs: Rhs) -> Self::Item {
413 validate_types(self.dtype(), &DataType::Int8)?;
414 let s = self.to_physical_repr();
415 Ok(apply_method_physical_numeric!(&s, lt_eq, rhs))
416 }
417}
418
419impl ChunkCompareEq<&str> for Series {
420 type Item = PolarsResult<BooleanChunked>;
421
422 fn equal(&self, rhs: &str) -> PolarsResult<BooleanChunked> {
423 validate_types(self.dtype(), &DataType::String)?;
424 match self.dtype() {
425 DataType::String => Ok(self.str().unwrap().equal(rhs)),
426 #[cfg(feature = "dtype-categorical")]
427 DataType::Categorical(_, _) | DataType::Enum(_, _) => Ok(
428 with_match_categorical_physical_type!(self.dtype().cat_physical().unwrap(), |$C| {
429 self.cat::<$C>().unwrap().equal(rhs)
430 }),
431 ),
432 #[cfg(feature = "dtype-extension")]
433 DataType::Extension(_, _) => self.ext().unwrap().storage().equal(rhs),
434 _ => Ok(BooleanChunked::full(self.name().clone(), false, self.len())),
435 }
436 }
437
438 fn equal_missing(&self, rhs: &str) -> Self::Item {
439 validate_types(self.dtype(), &DataType::String)?;
440 match self.dtype() {
441 DataType::String => Ok(self.str().unwrap().equal_missing(rhs)),
442 #[cfg(feature = "dtype-categorical")]
443 DataType::Categorical(_, _) | DataType::Enum(_, _) => Ok(
444 with_match_categorical_physical_type!(self.dtype().cat_physical().unwrap(), |$C| {
445 self.cat::<$C>().unwrap().equal_missing(rhs)
446 }),
447 ),
448 #[cfg(feature = "dtype-extension")]
449 DataType::Extension(_, _) => self.ext().unwrap().storage().equal_missing(rhs),
450 _ => Ok(replace_non_null(
451 self.name().clone(),
452 self.0.chunks(),
453 false,
454 )),
455 }
456 }
457
458 fn not_equal(&self, rhs: &str) -> PolarsResult<BooleanChunked> {
459 validate_types(self.dtype(), &DataType::String)?;
460 match self.dtype() {
461 DataType::String => Ok(self.str().unwrap().not_equal(rhs)),
462 #[cfg(feature = "dtype-categorical")]
463 DataType::Categorical(_, _) | DataType::Enum(_, _) => Ok(
464 with_match_categorical_physical_type!(self.dtype().cat_physical().unwrap(), |$C| {
465 self.cat::<$C>().unwrap().not_equal(rhs)
466 }),
467 ),
468 #[cfg(feature = "dtype-extension")]
469 DataType::Extension(_, _) => self.ext().unwrap().storage().not_equal(rhs),
470 _ => Ok(BooleanChunked::full(self.name().clone(), true, self.len())),
471 }
472 }
473
474 fn not_equal_missing(&self, rhs: &str) -> Self::Item {
475 validate_types(self.dtype(), &DataType::String)?;
476 match self.dtype() {
477 DataType::String => Ok(self.str().unwrap().not_equal_missing(rhs)),
478 #[cfg(feature = "dtype-categorical")]
479 DataType::Categorical(_, _) | DataType::Enum(_, _) => Ok(
480 with_match_categorical_physical_type!(self.dtype().cat_physical().unwrap(), |$C| {
481 self.cat::<$C>().unwrap().not_equal_missing(rhs)
482 }),
483 ),
484 #[cfg(feature = "dtype-extension")]
485 DataType::Extension(_, _) => self.ext().unwrap().storage().not_equal_missing(rhs),
486 _ => Ok(replace_non_null(self.name().clone(), self.0.chunks(), true)),
487 }
488 }
489}
490
491impl ChunkCompareIneq<&str> for Series {
492 type Item = PolarsResult<BooleanChunked>;
493
494 fn gt(&self, rhs: &str) -> Self::Item {
495 validate_types(self.dtype(), &DataType::String)?;
496 match self.dtype() {
497 DataType::String => Ok(self.str().unwrap().gt(rhs)),
498 #[cfg(feature = "dtype-categorical")]
499 DataType::Categorical(_, _) | DataType::Enum(_, _) => Ok(
500 with_match_categorical_physical_type!(self.dtype().cat_physical().unwrap(), |$C| {
501 self.cat::<$C>().unwrap().gt(rhs)
502 }),
503 ),
504 #[cfg(feature = "dtype-extension")]
505 DataType::Extension(_, _) => self.ext().unwrap().storage().gt(rhs),
506 _ => polars_bail!(
507 ComputeError: "cannot compare str value to series of type {}", self.dtype(),
508 ),
509 }
510 }
511
512 fn gt_eq(&self, rhs: &str) -> Self::Item {
513 validate_types(self.dtype(), &DataType::String)?;
514 match self.dtype() {
515 DataType::String => Ok(self.str().unwrap().gt_eq(rhs)),
516 #[cfg(feature = "dtype-categorical")]
517 DataType::Categorical(_, _) | DataType::Enum(_, _) => Ok(
518 with_match_categorical_physical_type!(self.dtype().cat_physical().unwrap(), |$C| {
519 self.cat::<$C>().unwrap().gt_eq(rhs)
520 }),
521 ),
522 #[cfg(feature = "dtype-extension")]
523 DataType::Extension(_, _) => self.ext().unwrap().storage().gt_eq(rhs),
524 _ => polars_bail!(
525 ComputeError: "cannot compare str value to series of type {}", self.dtype(),
526 ),
527 }
528 }
529
530 fn lt(&self, rhs: &str) -> Self::Item {
531 validate_types(self.dtype(), &DataType::String)?;
532 match self.dtype() {
533 DataType::String => Ok(self.str().unwrap().lt(rhs)),
534 #[cfg(feature = "dtype-categorical")]
535 DataType::Categorical(_, _) | DataType::Enum(_, _) => Ok(
536 with_match_categorical_physical_type!(self.dtype().cat_physical().unwrap(), |$C| {
537 self.cat::<$C>().unwrap().lt(rhs)
538 }),
539 ),
540 #[cfg(feature = "dtype-extension")]
541 DataType::Extension(_, _) => self.ext().unwrap().storage().lt(rhs),
542 _ => polars_bail!(
543 ComputeError: "cannot compare str value to series of type {}", self.dtype(),
544 ),
545 }
546 }
547
548 fn lt_eq(&self, rhs: &str) -> Self::Item {
549 validate_types(self.dtype(), &DataType::String)?;
550 match self.dtype() {
551 DataType::String => Ok(self.str().unwrap().lt_eq(rhs)),
552 #[cfg(feature = "dtype-categorical")]
553 DataType::Categorical(_, _) | DataType::Enum(_, _) => Ok(
554 with_match_categorical_physical_type!(self.dtype().cat_physical().unwrap(), |$C| {
555 self.cat::<$C>().unwrap().lt_eq(rhs)
556 }),
557 ),
558 #[cfg(feature = "dtype-extension")]
559 DataType::Extension(_, _) => self.ext().unwrap().storage().lt_eq(rhs),
560 _ => polars_bail!(
561 ComputeError: "cannot compare str value to series of type {}", self.dtype(),
562 ),
563 }
564 }
565}