Skip to main content

risingwave_frontend/expr/
pure.rs

1// Copyright 2023 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 std::borrow::Cow;
16
17use expr_node::Type;
18use risingwave_pb::expr::expr_node;
19
20use super::{ExprImpl, ExprVisitor};
21use crate::expr::FunctionCall;
22
23#[derive(Default)]
24pub(crate) struct ImpureAnalyzer {
25    impure: Option<Cow<'static, str>>,
26}
27
28impl ImpureAnalyzer {
29    /// Returns `true` if the expression is impure.
30    ///
31    /// Only call this method after visiting the expression.
32    pub fn is_impure(&self) -> bool {
33        self.impure.is_some()
34    }
35
36    /// Returns the description of the impure expression if it is impure, for error reporting.
37    /// `None` if the expression is pure.
38    ///
39    /// Only call this method after visiting the expression.
40    pub fn impure_expr_desc(&self) -> Option<&str> {
41        self.impure.as_deref()
42    }
43}
44
45impl ExprVisitor for ImpureAnalyzer {
46    fn visit_user_defined_function(&mut self, func_call: &super::UserDefinedFunction) {
47        let name = &func_call.catalog.name;
48        self.impure = Some(format!("user-defined function `{name}`").into());
49    }
50
51    fn visit_table_function(&mut self, func_call: &super::TableFunction) {
52        use crate::expr::table_function::TableFunctionType as Type;
53        let func_type = func_call.function_type;
54        match func_type {
55            Type::Unspecified => unreachable!(),
56
57            // deterministic
58            Type::GenerateSeries
59            | Type::Unnest
60            | Type::RegexpMatches
61            | Type::Range
62            | Type::GenerateSubscripts
63            | Type::PgExpandarray
64            | Type::JsonbArrayElements
65            | Type::JsonbArrayElementsText
66            | Type::JsonbEach
67            | Type::JsonbEachText
68            | Type::JsonbObjectKeys
69            | Type::JsonbPathQuery
70            | Type::JsonbPopulateRecordset
71            | Type::JsonbToRecordset => {
72                func_call.args.iter().for_each(|expr| self.visit_expr(expr));
73            }
74
75            // indeterministic
76            Type::FileScan
77            | Type::PostgresQuery
78            | Type::MysqlQuery
79            | Type::MssqlQuery
80            | Type::InternalBackfillProgress
81            | Type::InternalSourceBackfillProgress
82            | Type::InternalGetChannelDeltaStats
83            | Type::PgGetKeywords => {
84                self.impure = Some(func_type.as_str_name().into());
85            }
86            Type::UserDefined => {
87                let name = &func_call.user_defined.as_ref().unwrap().name;
88                self.impure = Some(format!("user-defined table function `{name}`").into());
89            }
90        }
91    }
92
93    fn visit_now(&mut self, _: &super::Now) {
94        self.impure = Some("NOW or PROCTIME".into());
95    }
96
97    fn visit_secret_ref(&mut self, secret_ref: &super::SecretRef) {
98        self.impure = Some(format!("secret reference `{}`", secret_ref.secret_name).into());
99    }
100
101    fn visit_function_call(&mut self, func_call: &super::FunctionCall) {
102        let func_type = func_call.func_type();
103        match func_type {
104            Type::Unspecified => unreachable!(),
105            #[expect(deprecated)]
106            Type::Add
107            | Type::Subtract
108            | Type::Multiply
109            | Type::Divide
110            | Type::Modulus
111            | Type::Equal
112            | Type::NotEqual
113            | Type::LessThan
114            | Type::LessThanOrEqual
115            | Type::GreaterThan
116            | Type::GreaterThanOrEqual
117            | Type::And
118            | Type::Or
119            | Type::Not
120            | Type::In
121            | Type::Some
122            | Type::All
123            | Type::BitwiseAnd
124            | Type::BitwiseOr
125            | Type::BitwiseXor
126            | Type::BitwiseNot
127            | Type::BitwiseShiftLeft
128            | Type::BitwiseShiftRight
129            | Type::Extract
130            | Type::DatePart
131            | Type::TumbleStart
132            | Type::SecToTimestamptz
133            | Type::AtTimeZone
134            | Type::DateTrunc
135            | Type::DateBin
136            | Type::MakeDate
137            | Type::MakeTime
138            | Type::MakeTimestamp
139            | Type::CharToTimestamptz
140            | Type::CharToDate
141            | Type::CastWithTimeZone
142            | Type::AddWithTimeZone
143            | Type::SubtractWithTimeZone
144            | Type::Cast
145            | Type::Substr
146            | Type::Length
147            | Type::Like
148            | Type::ILike
149            | Type::SimilarToEscape
150            | Type::Upper
151            | Type::Lower
152            | Type::Trim
153            | Type::Replace
154            | Type::Position
155            | Type::Ltrim
156            | Type::Rtrim
157            | Type::Case
158            | Type::ConstantLookup
159            | Type::RoundDigit
160            | Type::Round
161            | Type::Ascii
162            | Type::Translate
163            | Type::Coalesce
164            | Type::ConcatWs
165            | Type::ConcatWsVariadic
166            | Type::Abs
167            | Type::SplitPart
168            | Type::Ceil
169            | Type::Floor
170            | Type::Trunc
171            | Type::ToChar
172            | Type::Md5
173            | Type::CharLength
174            | Type::Repeat
175            | Type::ConcatOp
176            | Type::ByteaConcatOp
177            | Type::Concat
178            | Type::ConcatVariadic
179            | Type::BoolOut
180            | Type::OctetLength
181            | Type::BitLength
182            | Type::Overlay
183            | Type::RegexpMatch
184            | Type::RegexpReplace
185            | Type::RegexpCount
186            | Type::RegexpSplitToArray
187            | Type::RegexpEq
188            | Type::Pow
189            | Type::Exp
190            | Type::Ln
191            | Type::Log10
192            | Type::Chr
193            | Type::StartsWith
194            | Type::Initcap
195            | Type::Lpad
196            | Type::Rpad
197            | Type::Reverse
198            | Type::Strpos
199            | Type::ToAscii
200            | Type::ToHex
201            | Type::QuoteIdent
202            | Type::Sin
203            | Type::Cos
204            | Type::Tan
205            | Type::Cot
206            | Type::Asin
207            | Type::Acos
208            | Type::Acosd
209            | Type::Atan
210            | Type::Atan2
211            | Type::Atand
212            | Type::Atan2d
213            | Type::Sqrt
214            | Type::Cbrt
215            | Type::Sign
216            | Type::Scale
217            | Type::MinScale
218            | Type::TrimScale
219            | Type::Gamma
220            | Type::Lgamma
221            | Type::Left
222            | Type::Right
223            | Type::Degrees
224            | Type::Radians
225            | Type::IsTrue
226            | Type::IsNotTrue
227            | Type::IsFalse
228            | Type::IsNotFalse
229            | Type::IsNull
230            | Type::IsNotNull
231            | Type::IsDistinctFrom
232            | Type::IsNotDistinctFrom
233            | Type::Neg
234            | Type::Field
235            | Type::Array
236            | Type::ArrayAccess
237            | Type::ArrayRangeAccess
238            | Type::Row
239            | Type::ArrayToString
240            | Type::ArrayCat
241            | Type::ArrayMax
242            | Type::ArraySum
243            | Type::ArraySort
244            | Type::ArrayAppend
245            | Type::ArrayReverse
246            | Type::ArrayPrepend
247            | Type::FormatType
248            | Type::ArrayDistinct
249            | Type::ArrayMin
250            | Type::ArrayDims
251            | Type::ArrayLength
252            | Type::Cardinality
253            | Type::TrimArray
254            | Type::ArrayRemove
255            | Type::ArrayReplace
256            | Type::ArrayPosition
257            | Type::ArrayContains
258            | Type::ArrayContained
259            | Type::ArrayOverlaps
260            | Type::ArrayFlatten
261            | Type::HexToInt256
262            | Type::JsonbConcat
263            | Type::JsonbAccess
264            | Type::JsonbAccessStr
265            | Type::JsonbExtractPath
266            | Type::JsonbExtractPathVariadic
267            | Type::JsonbExtractPathText
268            | Type::JsonbExtractPathTextVariadic
269            | Type::JsonbTypeof
270            | Type::JsonbArrayLength
271            | Type::JsonbObject
272            | Type::JsonbPretty
273            | Type::JsonbDeletePath
274            | Type::JsonbContains
275            | Type::JsonbContained
276            | Type::JsonbExists
277            | Type::JsonbExistsAny
278            | Type::JsonbExistsAll
279            | Type::JsonbStripNulls
280            | Type::JsonbBuildArray
281            | Type::JsonbBuildArrayVariadic
282            | Type::JsonbBuildObject
283            | Type::JsonbPopulateRecord
284            | Type::JsonbToArray
285            | Type::JsonbToRecord
286            | Type::JsonbBuildObjectVariadic
287            | Type::JsonbPathExists
288            | Type::JsonbPathMatch
289            | Type::JsonbPathQueryArray
290            | Type::JsonbPathQueryFirst
291            | Type::JsonbSet
292            | Type::JsonbPopulateMap
293            | Type::IsJson
294            | Type::ToJsonb
295            | Type::ToVariant
296            | Type::VariantGet
297            | Type::TryVariantGet
298            | Type::VariantTypeof
299            | Type::Sind
300            | Type::Cosd
301            | Type::Cotd
302            | Type::Asind
303            | Type::Sinh
304            | Type::Cosh
305            | Type::Coth
306            | Type::Tanh
307            | Type::Atanh
308            | Type::Asinh
309            | Type::Acosh
310            | Type::Decode
311            | Type::Encode
312            | Type::GetBit
313            | Type::GetByte
314            | Type::SetBit
315            | Type::SetByte
316            | Type::BitCount
317            | Type::Sha1
318            | Type::Sha224
319            | Type::Sha256
320            | Type::Sha384
321            | Type::Sha512
322            | Type::Crc32
323            | Type::Crc32c
324            | Type::Hmac
325            | Type::SecureCompare
326            | Type::Decrypt
327            | Type::Encrypt
328            | Type::Tand
329            | Type::ArrayPositions
330            | Type::StringToArray
331            | Type::Format
332            | Type::FormatVariadic
333            | Type::PgwireSend
334            | Type::PgwireRecv
335            | Type::ArrayTransform
336            | Type::Greatest
337            | Type::Least
338            | Type::ConvertFrom
339            | Type::ConvertTo
340            | Type::IcebergTransform
341            | Type::InetNtoa
342            | Type::InetAton
343            | Type::QuoteLiteral
344            | Type::QuoteNullable
345            | Type::MapFromEntries
346            | Type::MapAccess
347            | Type::MapKeys
348            | Type::MapValues
349            | Type::MapEntries
350            | Type::MapFromKeyValues
351            | Type::MapCat
352            | Type::MapContains
353            | Type::MapDelete
354            | Type::MapFilter
355            | Type::MapInsert
356            | Type::MapLength
357            | Type::L2Distance
358            | Type::CosineDistance
359            | Type::L1Distance
360            | Type::InnerProduct
361            | Type::VecConcat
362            | Type::L2Norm
363            | Type::L2Normalize
364            | Type::Subvector
365            // TODO: `rw_vnode` is more like STABLE instead of IMMUTABLE, because even its result is
366            // deterministic, it needs to read the total vnode count from the context, which means that
367            // it cannot be evaluated during constant folding. We have to treat it pure here so it can be used
368            // internally without materialization.
369            | Type::Vnode
370            | Type::VnodeUser
371            | Type::RwEpochToTs
372            | Type::CheckNotNull
373            | Type::CompositeCast =>
374            // expression output is deterministic(same result for the same input)
375            {
376                func_call
377                    .inputs()
378                    .iter()
379                    .for_each(|expr| self.visit_expr(expr));
380            }
381            // expression output is not deterministic
382            Type::TestFeature
383            | Type::License
384            | Type::Proctime
385            | Type::PgSleep
386            | Type::PgSleepFor
387            | Type::PgSleepUntil
388            | Type::CastRegclass
389            | Type::PgGetIndexdef
390            | Type::ColDescription
391            | Type::PgGetViewdef
392            | Type::PgGetUserbyid
393            | Type::PgIndexesSize
394            | Type::PgRelationSize
395            | Type::PgGetSerialSequence
396            | Type::PgIndexColumnHasProperty
397            | Type::HasTablePrivilege
398            | Type::HasAnyColumnPrivilege
399            | Type::HasSchemaPrivilege
400            | Type::MakeTimestamptz
401            | Type::PgIsInRecovery
402            | Type::RwRecoveryStatus
403            | Type::RwClusterId
404            | Type::RwFragmentVnodes
405            | Type::RwActorVnodes
406            | Type::PgTableIsVisible
407            | Type::HasFunctionPrivilege
408            | Type::OpenaiEmbedding
409            | Type::HasDatabasePrivilege
410            | Type::Random
411            | Type::ClockTimestamp
412            | Type::GenRandomUuid => self.impure = Some(func_type.as_str_name().into()),
413        }
414    }
415}
416
417pub fn is_pure(expr: &ExprImpl) -> bool {
418    !is_impure(expr)
419}
420
421pub fn is_impure(expr: &ExprImpl) -> bool {
422    let mut a = ImpureAnalyzer::default();
423    a.visit_expr(expr);
424    a.is_impure()
425}
426
427pub fn is_impure_func_call(func_call: &FunctionCall) -> bool {
428    let mut a = ImpureAnalyzer::default();
429    a.visit_function_call(func_call);
430    a.is_impure()
431}
432
433/// Returns the description of the impure expression if it is impure, for error reporting.
434/// `None` if the expression is pure.
435pub fn impure_expr_desc(expr: &ExprImpl) -> Option<String> {
436    let mut a = ImpureAnalyzer::default();
437    a.visit_expr(expr);
438    a.impure_expr_desc().map(|s| s.to_owned())
439}
440
441#[cfg(test)]
442mod tests {
443    use risingwave_common::types::DataType;
444    use risingwave_pb::expr::expr_node::Type;
445
446    use crate::expr::{ExprImpl, FunctionCall, InputRef, is_impure, is_pure};
447
448    fn expect_pure(expr: &ExprImpl) {
449        assert!(is_pure(expr));
450        assert!(!is_impure(expr));
451    }
452
453    fn expect_impure(expr: &ExprImpl) {
454        assert!(!is_pure(expr));
455        assert!(is_impure(expr));
456    }
457
458    #[test]
459    fn test_pure_funcs() {
460        let e: ExprImpl = FunctionCall::new(
461            Type::Add,
462            vec![
463                InputRef::new(0, DataType::Int16).into(),
464                InputRef::new(0, DataType::Int16).into(),
465            ],
466        )
467        .unwrap()
468        .into();
469        expect_pure(&e);
470
471        let e: ExprImpl = FunctionCall::new(
472            Type::GreaterThan,
473            vec![
474                InputRef::new(0, DataType::Timestamptz).into(),
475                FunctionCall::new(Type::Proctime, vec![]).unwrap().into(),
476            ],
477        )
478        .unwrap()
479        .into();
480        expect_impure(&e);
481    }
482}