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}