risingwave_expr_impl/scalar/
position.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use risingwave_expr::function;
16
17/// Returns the index of the first occurrence of the specified substring in the input string,
18/// or zero if the substring is not present.
19///
20/// # Example
21///
22/// ```slt
23/// query I
24/// select position('om' in 'Thomas');
25/// ----
26/// 3
27///
28/// query I
29/// select strpos('hello, world', 'lo');
30/// ----
31/// 4
32///
33/// query I
34/// select strpos('high', 'ig');
35/// ----
36/// 2
37///
38/// query I
39/// select strpos('abc', 'def');
40/// ----
41/// 0
42///
43/// query I
44/// select strpos('床前明月光', '月光');
45/// ----
46/// 4
47/// ```
48#[function("strpos(varchar, varchar) -> int4", deprecated)]
49#[function("position(varchar, varchar) -> int4")]
50pub fn position(str: &str, sub_str: &str) -> i32 {
51    match str.find(sub_str) {
52        Some(byte_idx) => (str[..byte_idx].chars().count() + 1) as i32,
53        None => 0,
54    }
55}
56
57/// Returns the index of the first occurrence of the specified bytea substring in the input bytea,
58/// or zero if the substring is not present.
59///
60/// # Example
61///
62/// ```slt
63/// query I
64/// select position('\x6c6f'::bytea in '\x68656c6c6f2c20776f726c64'::bytea);
65/// ----
66/// 4
67///
68/// query I
69/// select position('\x6967'::bytea in '\x68696768'::bytea);
70/// ----
71/// 2
72///
73/// query I
74/// select position('\x64'::bytea in '\x616263'::bytea);
75/// ----
76/// 0
77///
78/// query I
79/// select position(''::bytea in '\x616263'::bytea);
80/// ----
81/// 1
82///
83/// query I
84/// select position('\x616263'::bytea in ''::bytea);
85/// ----
86/// 0
87/// ```
88#[function("position(bytea, bytea) -> int4")]
89pub fn bytea_position(bytea: &[u8], sub_bytea: &[u8]) -> i32 {
90    if sub_bytea.is_empty() {
91        return 1;
92    }
93    if sub_bytea.len() > bytea.len() {
94        return 0;
95    }
96    let mut i = 0;
97    while i <= bytea.len().saturating_sub(sub_bytea.len()) {
98        if &bytea[i..i + sub_bytea.len()] == sub_bytea {
99            return (i + 1) as i32;
100        }
101        i += 1;
102    }
103    0
104}
105
106#[cfg(test)]
107mod tests {
108
109    use super::*;
110
111    #[test]
112    fn test_length() {
113        let cases = [
114            ("hello world", "world", 7),
115            ("床前明月光", "月光", 4),
116            ("床前明月光", "故乡", 0),
117        ];
118
119        for (str, sub_str, expected) in cases {
120            assert_eq!(position(str, sub_str), expected)
121        }
122    }
123
124    #[test]
125    fn test_bytea_position() {
126        let cases: [(&[u8], &[u8], i32); 3] = [
127            (b"\x01\x02\x03", b"\x03", 3),
128            (b"\x01\x02\x03", b"\x04", 0),
129            (b"\x01\x02\x03", b"", 1),
130        ];
131        for (bytea, sub_bytea, expected) in cases {
132            assert_eq!(bytea_position(bytea, sub_bytea), expected)
133        }
134    }
135}