From 02a3985548bfa41cf8d7aa93235c2be76efe0233 Mon Sep 17 00:00:00 2001 From: Eric Fu Date: Wed, 17 Apr 2024 21:24:50 +0800 Subject: [PATCH] feat: support `INET_ATON` and `INET_NTOA` (#16358) --- clippy.toml | 3 +- proto/expr.proto | 2 + src/expr/impl/src/scalar/inet.rs | 134 ++++++ src/expr/impl/src/scalar/mod.rs | 1 + src/frontend/src/binder/expr/function.rs | 2 + src/frontend/src/expr/pure.rs | 451 +++++++++--------- .../src/optimizer/plan_expr_visitor/strong.rs | 4 +- 7 files changed, 371 insertions(+), 226 deletions(-) create mode 100644 src/expr/impl/src/scalar/inet.rs diff --git a/clippy.toml b/clippy.toml index bbda73f322593..551de0eb6c479 100644 --- a/clippy.toml +++ b/clippy.toml @@ -33,7 +33,8 @@ doc-valid-idents = [ "PostgreSQL", "MySQL", "TopN", - "VNode" + "VNode", + "IPv4", ] avoid-breaking-exported-api = false upper-case-acronyms-aggressive = true diff --git a/proto/expr.proto b/proto/expr.proto index 4c55ee2b614b9..48fe88d1ff49c 100644 --- a/proto/expr.proto +++ b/proto/expr.proto @@ -192,6 +192,8 @@ message ExprNode { CONVERT_TO = 323; DECRYPT = 324; ENCRYPT = 325; + INET_ATON = 328; + INET_NTOA = 329; // Unary operators NEG = 401; diff --git a/src/expr/impl/src/scalar/inet.rs b/src/expr/impl/src/scalar/inet.rs new file mode 100644 index 0000000000000..71be67c1f3b3a --- /dev/null +++ b/src/expr/impl/src/scalar/inet.rs @@ -0,0 +1,134 @@ +// Copyright 2024 RisingWave Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use risingwave_expr::{function, ExprError, Result}; + +/// Given the dotted-quad representation of an IPv4 network address as a string, +/// returns an integer that represents the numeric value of the address +/// in network byte order (big endian). The returning value is a BIGINT (8-byte integer) +/// because PG doesn't support unsigned 32-bit integer. +/// +/// Short-form IP addresses (such as '127.1' as a representation of '127.0.0.1') +/// are NOT supported. +/// +/// This function is ported from MySQL. +/// Ref: . +/// +/// # Example +/// +/// ```slt +/// query I +/// select inet_aton('10.0.5.9'); +/// ---- +/// 167773449 +/// ``` +#[function("inet_aton(varchar) -> int8")] +pub fn inet_aton(str: &str) -> Result { + let mut parts = str.split('.'); + let mut result = 0; + for _ in 0..4 { + let part = parts.next().ok_or(ExprError::InvalidParam { + name: "str", + reason: format!("Invalid IP address: {}", &str).into(), + })?; + let part = part.parse::().map_err(|_| ExprError::InvalidParam { + name: "str", + reason: format!("Invalid IP address: {}", &str).into(), + })?; + result = (result << 8) | part as i64; + } + Ok(result) +} + +/// Given a numeric IPv4 network address in network byte order (big endian), +/// returns the dotted-quad string representation of the address as a string. +/// +/// This function is ported from MySQL. +/// Ref: . +/// +/// # Example +/// +/// ```slt +/// query T +/// select inet_ntoa(167773449); +/// ---- +/// 10.0.5.9 +/// ``` +#[function("inet_ntoa(int8) -> varchar")] +pub fn inet_ntoa(mut num: i64) -> Result> { + if (num > u32::MAX as i64) || (num < 0) { + return Err(ExprError::InvalidParam { + name: "num", + reason: format!("Invalid IP number: {}", num).into(), + }); + } + let mut parts = [0u8, 0, 0, 0]; + for i in (0..4).rev() { + parts[i] = (num & 0xFF) as u8; + num >>= 8; + } + let str = parts + .iter() + .map(|&x| x.to_string()) + .collect::>() + .join("."); + Ok(str.into_boxed_str()) +} + +#[cfg(test)] +mod tests { + use std::assert_matches::assert_matches; + + use super::*; + + #[test] + fn test_inet_aton() { + assert_eq!(inet_aton("10.0.5.9").unwrap(), 167773449); + assert_eq!(inet_aton("203.117.31.34").unwrap(), 3413450530); + + if let ExprError::InvalidParam { name, reason } = inet_aton("127.1").unwrap_err() { + assert_eq!(name, "str"); + assert_eq!(reason, "Invalid IP address: 127.1".into()); + } else { + panic!("Expected InvalidParam error"); + } + + assert_matches!(inet_aton("127.0.1"), Err(ExprError::InvalidParam { .. })); + assert_matches!(inet_aton("1.0.0.256"), Err(ExprError::InvalidParam { .. })); + assert_matches!(inet_aton("1.0.0.-1"), Err(ExprError::InvalidParam { .. })); + } + + #[test] + fn test_inet_ntoa() { + assert_eq!(inet_ntoa(167773449).unwrap(), "10.0.5.9".into()); + assert_eq!(inet_ntoa(3413450530).unwrap(), "203.117.31.34".into()); + assert_eq!(inet_ntoa(0).unwrap(), "0.0.0.0".into()); + assert_eq!( + inet_ntoa(u32::MAX as i64).unwrap(), + "255.255.255.255".into() + ); + + if let ExprError::InvalidParam { name, reason } = inet_ntoa(-1).unwrap_err() { + assert_eq!(name, "num"); + assert_eq!(reason, "Invalid IP number: -1".into()); + } else { + panic!("Expected InvalidParam error"); + } + + assert_matches!( + inet_ntoa(u32::MAX as i64 + 1), + Err(ExprError::InvalidParam { .. }) + ); + } +} diff --git a/src/expr/impl/src/scalar/mod.rs b/src/expr/impl/src/scalar/mod.rs index d2f528ece0c6e..edbaaf4de01ab 100644 --- a/src/expr/impl/src/scalar/mod.rs +++ b/src/expr/impl/src/scalar/mod.rs @@ -82,6 +82,7 @@ mod vnode; pub use to_jsonb::*; mod encrypt; mod external; +mod inet; mod to_timestamp; mod translate; mod trigonometric; diff --git a/src/frontend/src/binder/expr/function.rs b/src/frontend/src/binder/expr/function.rs index 1b36e9ee2fd73..2134e08fe8c66 100644 --- a/src/frontend/src/binder/expr/function.rs +++ b/src/frontend/src/binder/expr/function.rs @@ -1026,6 +1026,8 @@ impl Binder { ("decrypt", raw_call(ExprType::Decrypt)), ("left", raw_call(ExprType::Left)), ("right", raw_call(ExprType::Right)), + ("inet_aton", raw_call(ExprType::InetAton)), + ("inet_ntoa", raw_call(ExprType::InetNtoa)), ("int8send", raw_call(ExprType::PgwireSend)), ("int8recv", guard_by_len(1, raw(|_binder, mut inputs| { // Similar to `cast` from string, return type is set explicitly rather than inferred. diff --git a/src/frontend/src/expr/pure.rs b/src/frontend/src/expr/pure.rs index 310e7a9187dab..bc18959e5be55 100644 --- a/src/frontend/src/expr/pure.rs +++ b/src/frontend/src/expr/pure.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use expr_node::Type; use risingwave_pb::expr::expr_node; use super::{ExprImpl, ExprVisitor}; @@ -33,216 +34,218 @@ impl ExprVisitor for ImpureAnalyzer { fn visit_function_call(&mut self, func_call: &super::FunctionCall) { match func_call.func_type() { - expr_node::Type::Unspecified => unreachable!(), - expr_node::Type::Add - | expr_node::Type::Subtract - | expr_node::Type::Multiply - | expr_node::Type::Divide - | expr_node::Type::Modulus - | expr_node::Type::Equal - | expr_node::Type::NotEqual - | expr_node::Type::LessThan - | expr_node::Type::LessThanOrEqual - | expr_node::Type::GreaterThan - | expr_node::Type::GreaterThanOrEqual - | expr_node::Type::And - | expr_node::Type::Or - | expr_node::Type::Not - | expr_node::Type::In - | expr_node::Type::Some - | expr_node::Type::All - | expr_node::Type::BitwiseAnd - | expr_node::Type::BitwiseOr - | expr_node::Type::BitwiseXor - | expr_node::Type::BitwiseNot - | expr_node::Type::BitwiseShiftLeft - | expr_node::Type::BitwiseShiftRight - | expr_node::Type::Extract - | expr_node::Type::DatePart - | expr_node::Type::TumbleStart - | expr_node::Type::SecToTimestamptz - | expr_node::Type::AtTimeZone - | expr_node::Type::DateTrunc - | expr_node::Type::MakeDate - | expr_node::Type::MakeTime - | expr_node::Type::MakeTimestamp - | expr_node::Type::CharToTimestamptz - | expr_node::Type::CharToDate - | expr_node::Type::CastWithTimeZone - | expr_node::Type::AddWithTimeZone - | expr_node::Type::SubtractWithTimeZone - | expr_node::Type::Cast - | expr_node::Type::Substr - | expr_node::Type::Length - | expr_node::Type::Like - | expr_node::Type::ILike - | expr_node::Type::SimilarToEscape - | expr_node::Type::Upper - | expr_node::Type::Lower - | expr_node::Type::Trim - | expr_node::Type::Replace - | expr_node::Type::Position - | expr_node::Type::Ltrim - | expr_node::Type::Rtrim - | expr_node::Type::Case - | expr_node::Type::ConstantLookup - | expr_node::Type::RoundDigit - | expr_node::Type::Round - | expr_node::Type::Ascii - | expr_node::Type::Translate - | expr_node::Type::Coalesce - | expr_node::Type::ConcatWs - | expr_node::Type::ConcatWsVariadic - | expr_node::Type::Abs - | expr_node::Type::SplitPart - | expr_node::Type::Ceil - | expr_node::Type::Floor - | expr_node::Type::Trunc - | expr_node::Type::ToChar - | expr_node::Type::Md5 - | expr_node::Type::CharLength - | expr_node::Type::Repeat - | expr_node::Type::ConcatOp - | expr_node::Type::Concat - | expr_node::Type::ConcatVariadic - | expr_node::Type::BoolOut - | expr_node::Type::OctetLength - | expr_node::Type::BitLength - | expr_node::Type::Overlay - | expr_node::Type::RegexpMatch - | expr_node::Type::RegexpReplace - | expr_node::Type::RegexpCount - | expr_node::Type::RegexpSplitToArray - | expr_node::Type::RegexpEq - | expr_node::Type::Pow - | expr_node::Type::Exp - | expr_node::Type::Ln - | expr_node::Type::Log10 - | expr_node::Type::Chr - | expr_node::Type::StartsWith - | expr_node::Type::Initcap - | expr_node::Type::Lpad - | expr_node::Type::Rpad - | expr_node::Type::Reverse - | expr_node::Type::Strpos - | expr_node::Type::ToAscii - | expr_node::Type::ToHex - | expr_node::Type::QuoteIdent - | expr_node::Type::Sin - | expr_node::Type::Cos - | expr_node::Type::Tan - | expr_node::Type::Cot - | expr_node::Type::Asin - | expr_node::Type::Acos - | expr_node::Type::Atan - | expr_node::Type::Atan2 - | expr_node::Type::Sqrt - | expr_node::Type::Cbrt - | expr_node::Type::Sign - | expr_node::Type::Scale - | expr_node::Type::MinScale - | expr_node::Type::TrimScale - | expr_node::Type::Left - | expr_node::Type::Right - | expr_node::Type::Degrees - | expr_node::Type::Radians - | expr_node::Type::IsTrue - | expr_node::Type::IsNotTrue - | expr_node::Type::IsFalse - | expr_node::Type::IsNotFalse - | expr_node::Type::IsNull - | expr_node::Type::IsNotNull - | expr_node::Type::IsDistinctFrom - | expr_node::Type::IsNotDistinctFrom - | expr_node::Type::Neg - | expr_node::Type::Field - | expr_node::Type::Array - | expr_node::Type::ArrayAccess - | expr_node::Type::ArrayRangeAccess - | expr_node::Type::Row - | expr_node::Type::ArrayToString - | expr_node::Type::ArrayCat - | expr_node::Type::ArrayMax - | expr_node::Type::ArraySum - | expr_node::Type::ArraySort - | expr_node::Type::ArrayAppend - | expr_node::Type::ArrayPrepend - | expr_node::Type::FormatType - | expr_node::Type::ArrayDistinct - | expr_node::Type::ArrayMin - | expr_node::Type::ArrayDims - | expr_node::Type::ArrayLength - | expr_node::Type::Cardinality - | expr_node::Type::TrimArray - | expr_node::Type::ArrayRemove - | expr_node::Type::ArrayReplace - | expr_node::Type::ArrayPosition - | expr_node::Type::ArrayContains - | expr_node::Type::ArrayContained - | expr_node::Type::HexToInt256 - | expr_node::Type::JsonbConcat - | expr_node::Type::JsonbAccess - | expr_node::Type::JsonbAccessStr - | expr_node::Type::JsonbExtractPath - | expr_node::Type::JsonbExtractPathVariadic - | expr_node::Type::JsonbExtractPathText - | expr_node::Type::JsonbExtractPathTextVariadic - | expr_node::Type::JsonbTypeof - | expr_node::Type::JsonbArrayLength - | expr_node::Type::JsonbObject - | expr_node::Type::JsonbPretty - | expr_node::Type::JsonbDeletePath - | expr_node::Type::JsonbContains - | expr_node::Type::JsonbContained - | expr_node::Type::JsonbExists - | expr_node::Type::JsonbExistsAny - | expr_node::Type::JsonbExistsAll - | expr_node::Type::JsonbStripNulls - | expr_node::Type::JsonbBuildArray - | expr_node::Type::JsonbBuildArrayVariadic - | expr_node::Type::JsonbBuildObject - | expr_node::Type::JsonbPopulateRecord - | expr_node::Type::JsonbToRecord - | expr_node::Type::JsonbBuildObjectVariadic - | expr_node::Type::JsonbPathExists - | expr_node::Type::JsonbPathMatch - | expr_node::Type::JsonbPathQueryArray - | expr_node::Type::JsonbPathQueryFirst - | expr_node::Type::IsJson - | expr_node::Type::ToJsonb - | expr_node::Type::Sind - | expr_node::Type::Cosd - | expr_node::Type::Cotd - | expr_node::Type::Asind - | expr_node::Type::Sinh - | expr_node::Type::Cosh - | expr_node::Type::Coth - | expr_node::Type::Tanh - | expr_node::Type::Atanh - | expr_node::Type::Asinh - | expr_node::Type::Acosh - | expr_node::Type::Decode - | expr_node::Type::Encode - | expr_node::Type::Sha1 - | expr_node::Type::Sha224 - | expr_node::Type::Sha256 - | expr_node::Type::Sha384 - | expr_node::Type::Sha512 - | expr_node::Type::Decrypt - | expr_node::Type::Encrypt - | expr_node::Type::Tand - | expr_node::Type::ArrayPositions - | expr_node::Type::StringToArray - | expr_node::Type::Format - | expr_node::Type::FormatVariadic - | expr_node::Type::PgwireSend - | expr_node::Type::PgwireRecv - | expr_node::Type::ArrayTransform - | expr_node::Type::Greatest - | expr_node::Type::Least - | expr_node::Type::ConvertFrom - | expr_node::Type::ConvertTo - | expr_node::Type::IcebergTransform => + Type::Unspecified => unreachable!(), + Type::Add + | Type::Subtract + | Type::Multiply + | Type::Divide + | Type::Modulus + | Type::Equal + | Type::NotEqual + | Type::LessThan + | Type::LessThanOrEqual + | Type::GreaterThan + | Type::GreaterThanOrEqual + | Type::And + | Type::Or + | Type::Not + | Type::In + | Type::Some + | Type::All + | Type::BitwiseAnd + | Type::BitwiseOr + | Type::BitwiseXor + | Type::BitwiseNot + | Type::BitwiseShiftLeft + | Type::BitwiseShiftRight + | Type::Extract + | Type::DatePart + | Type::TumbleStart + | Type::SecToTimestamptz + | Type::AtTimeZone + | Type::DateTrunc + | Type::MakeDate + | Type::MakeTime + | Type::MakeTimestamp + | Type::CharToTimestamptz + | Type::CharToDate + | Type::CastWithTimeZone + | Type::AddWithTimeZone + | Type::SubtractWithTimeZone + | Type::Cast + | Type::Substr + | Type::Length + | Type::Like + | Type::ILike + | Type::SimilarToEscape + | Type::Upper + | Type::Lower + | Type::Trim + | Type::Replace + | Type::Position + | Type::Ltrim + | Type::Rtrim + | Type::Case + | Type::ConstantLookup + | Type::RoundDigit + | Type::Round + | Type::Ascii + | Type::Translate + | Type::Coalesce + | Type::ConcatWs + | Type::ConcatWsVariadic + | Type::Abs + | Type::SplitPart + | Type::Ceil + | Type::Floor + | Type::Trunc + | Type::ToChar + | Type::Md5 + | Type::CharLength + | Type::Repeat + | Type::ConcatOp + | Type::Concat + | Type::ConcatVariadic + | Type::BoolOut + | Type::OctetLength + | Type::BitLength + | Type::Overlay + | Type::RegexpMatch + | Type::RegexpReplace + | Type::RegexpCount + | Type::RegexpSplitToArray + | Type::RegexpEq + | Type::Pow + | Type::Exp + | Type::Ln + | Type::Log10 + | Type::Chr + | Type::StartsWith + | Type::Initcap + | Type::Lpad + | Type::Rpad + | Type::Reverse + | Type::Strpos + | Type::ToAscii + | Type::ToHex + | Type::QuoteIdent + | Type::Sin + | Type::Cos + | Type::Tan + | Type::Cot + | Type::Asin + | Type::Acos + | Type::Atan + | Type::Atan2 + | Type::Sqrt + | Type::Cbrt + | Type::Sign + | Type::Scale + | Type::MinScale + | Type::TrimScale + | Type::Left + | Type::Right + | Type::Degrees + | Type::Radians + | Type::IsTrue + | Type::IsNotTrue + | Type::IsFalse + | Type::IsNotFalse + | Type::IsNull + | Type::IsNotNull + | Type::IsDistinctFrom + | Type::IsNotDistinctFrom + | Type::Neg + | Type::Field + | Type::Array + | Type::ArrayAccess + | Type::ArrayRangeAccess + | Type::Row + | Type::ArrayToString + | Type::ArrayCat + | Type::ArrayMax + | Type::ArraySum + | Type::ArraySort + | Type::ArrayAppend + | Type::ArrayPrepend + | Type::FormatType + | Type::ArrayDistinct + | Type::ArrayMin + | Type::ArrayDims + | Type::ArrayLength + | Type::Cardinality + | Type::TrimArray + | Type::ArrayRemove + | Type::ArrayReplace + | Type::ArrayPosition + | Type::ArrayContains + | Type::ArrayContained + | Type::HexToInt256 + | Type::JsonbConcat + | Type::JsonbAccess + | Type::JsonbAccessStr + | Type::JsonbExtractPath + | Type::JsonbExtractPathVariadic + | Type::JsonbExtractPathText + | Type::JsonbExtractPathTextVariadic + | Type::JsonbTypeof + | Type::JsonbArrayLength + | Type::JsonbObject + | Type::JsonbPretty + | Type::JsonbDeletePath + | Type::JsonbContains + | Type::JsonbContained + | Type::JsonbExists + | Type::JsonbExistsAny + | Type::JsonbExistsAll + | Type::JsonbStripNulls + | Type::JsonbBuildArray + | Type::JsonbBuildArrayVariadic + | Type::JsonbBuildObject + | Type::JsonbPopulateRecord + | Type::JsonbToRecord + | Type::JsonbBuildObjectVariadic + | Type::JsonbPathExists + | Type::JsonbPathMatch + | Type::JsonbPathQueryArray + | Type::JsonbPathQueryFirst + | Type::IsJson + | Type::ToJsonb + | Type::Sind + | Type::Cosd + | Type::Cotd + | Type::Asind + | Type::Sinh + | Type::Cosh + | Type::Coth + | Type::Tanh + | Type::Atanh + | Type::Asinh + | Type::Acosh + | Type::Decode + | Type::Encode + | Type::Sha1 + | Type::Sha224 + | Type::Sha256 + | Type::Sha384 + | Type::Sha512 + | Type::Decrypt + | Type::Encrypt + | Type::Tand + | Type::ArrayPositions + | Type::StringToArray + | Type::Format + | Type::FormatVariadic + | Type::PgwireSend + | Type::PgwireRecv + | Type::ArrayTransform + | Type::Greatest + | Type::Least + | Type::ConvertFrom + | Type::ConvertTo + | Type::IcebergTransform + | Type::InetNtoa + | Type::InetAton => // expression output is deterministic(same result for the same input) { func_call @@ -251,20 +254,20 @@ impl ExprVisitor for ImpureAnalyzer { .for_each(|expr| self.visit_expr(expr)); } // expression output is not deterministic - expr_node::Type::Vnode - | expr_node::Type::Proctime - | expr_node::Type::PgSleep - | expr_node::Type::PgSleepFor - | expr_node::Type::PgSleepUntil - | expr_node::Type::CastRegclass - | expr_node::Type::PgGetIndexdef - | expr_node::Type::ColDescription - | expr_node::Type::PgGetViewdef - | expr_node::Type::PgGetUserbyid - | expr_node::Type::PgIndexesSize - | expr_node::Type::PgRelationSize - | expr_node::Type::PgGetSerialSequence - | expr_node::Type::MakeTimestamptz => self.impure = true, + Type::Vnode + | Type::Proctime + | Type::PgSleep + | Type::PgSleepFor + | Type::PgSleepUntil + | Type::CastRegclass + | Type::PgGetIndexdef + | Type::ColDescription + | Type::PgGetViewdef + | Type::PgGetUserbyid + | Type::PgIndexesSize + | Type::PgRelationSize + | Type::PgGetSerialSequence + | Type::MakeTimestamptz => self.impure = true, } } } diff --git a/src/frontend/src/optimizer/plan_expr_visitor/strong.rs b/src/frontend/src/optimizer/plan_expr_visitor/strong.rs index a3d2e0edfb15d..e30cdb0b6e314 100644 --- a/src/frontend/src/optimizer/plan_expr_visitor/strong.rs +++ b/src/frontend/src/optimizer/plan_expr_visitor/strong.rs @@ -300,7 +300,9 @@ impl Strong { | ExprType::PgIndexesSize | ExprType::PgRelationSize | ExprType::PgGetSerialSequence - | ExprType::IcebergTransform => false, + | ExprType::IcebergTransform + | ExprType::InetAton + | ExprType::InetNtoa => false, ExprType::Unspecified => unreachable!(), } }