1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
from __future__ import annotations
from typing import TYPE_CHECKING
from duckdb import FunctionExpression
from narwhals._duckdb.utils import lit
from narwhals._utils import not_implemented
if TYPE_CHECKING:
from duckdb import Expression
from narwhals._duckdb.expr import DuckDBExpr
class DuckDBExprStringNamespace:
def __init__(self, expr: DuckDBExpr) -> None:
self._compliant_expr = expr
def starts_with(self, prefix: str) -> DuckDBExpr:
return self._compliant_expr._with_callable(
lambda expr: FunctionExpression("starts_with", expr, lit(prefix))
)
def ends_with(self, suffix: str) -> DuckDBExpr:
return self._compliant_expr._with_callable(
lambda expr: FunctionExpression("ends_with", expr, lit(suffix))
)
def contains(self, pattern: str, *, literal: bool) -> DuckDBExpr:
def func(expr: Expression) -> Expression:
if literal:
return FunctionExpression("contains", expr, lit(pattern))
return FunctionExpression("regexp_matches", expr, lit(pattern))
return self._compliant_expr._with_callable(func)
def slice(self, offset: int, length: int) -> DuckDBExpr:
def func(expr: Expression) -> Expression:
offset_lit = lit(offset)
return FunctionExpression(
"array_slice",
expr,
lit(offset + 1)
if offset >= 0
else FunctionExpression("length", expr) + offset_lit + lit(1),
FunctionExpression("length", expr)
if length is None
else lit(length) + offset_lit,
)
return self._compliant_expr._with_callable(func)
def split(self, by: str) -> DuckDBExpr:
return self._compliant_expr._with_callable(
lambda expr: FunctionExpression("str_split", expr, lit(by))
)
def len_chars(self) -> DuckDBExpr:
return self._compliant_expr._with_callable(
lambda expr: FunctionExpression("length", expr)
)
def to_lowercase(self) -> DuckDBExpr:
return self._compliant_expr._with_callable(
lambda expr: FunctionExpression("lower", expr)
)
def to_uppercase(self) -> DuckDBExpr:
return self._compliant_expr._with_callable(
lambda expr: FunctionExpression("upper", expr)
)
def strip_chars(self, characters: str | None) -> DuckDBExpr:
import string
return self._compliant_expr._with_callable(
lambda expr: FunctionExpression(
"trim", expr, lit(string.whitespace if characters is None else characters)
)
)
def replace_all(self, pattern: str, value: str, *, literal: bool) -> DuckDBExpr:
if not literal:
return self._compliant_expr._with_callable(
lambda expr: FunctionExpression(
"regexp_replace", expr, lit(pattern), lit(value), lit("g")
)
)
return self._compliant_expr._with_callable(
lambda expr: FunctionExpression("replace", expr, lit(pattern), lit(value))
)
def to_datetime(self, format: str | None) -> DuckDBExpr:
if format is None:
msg = "Cannot infer format with DuckDB backend, please specify `format` explicitly."
raise NotImplementedError(msg)
return self._compliant_expr._with_callable(
lambda expr: FunctionExpression("strptime", expr, lit(format))
)
replace = not_implemented()
|