From ebcdd9001a0d0b1097eae191a4ab4a963fb22e2b Mon Sep 17 00:00:00 2001 From: Eric Fu Date: Mon, 20 May 2024 18:00:55 +0800 Subject: [PATCH] rewrite in binder --- src/expr/impl/src/scalar/string.rs | 101 ++++------------------- src/frontend/src/binder/expr/function.rs | 26 +++++- src/tests/regress/data/sql/text.sql | 6 +- 3 files changed, 42 insertions(+), 91 deletions(-) diff --git a/src/expr/impl/src/scalar/string.rs b/src/expr/impl/src/scalar/string.rs index 14049f6f19218..32ba0787d75b7 100644 --- a/src/expr/impl/src/scalar/string.rs +++ b/src/expr/impl/src/scalar/string.rs @@ -18,9 +18,7 @@ use std::fmt::Write; -use risingwave_common::types::ScalarRefImpl; -use risingwave_expr::{function, ExprError, Result}; -use thiserror_ext::AsReport; +use risingwave_expr::function; /// Returns the character with the specified Unicode code point. /// @@ -475,6 +473,7 @@ pub fn right(s: &str, n: i32, writer: &mut impl Write) { } /// `quote_literal(string text)` +/// `quote_literal(value anyelement)` /// /// Return the given string suitably quoted to be used as a string literal in an SQL statement /// string. Embedded single-quotes and backslashes are properly doubled. @@ -495,6 +494,21 @@ pub fn right(s: &str, n: i32, writer: &mut impl Write) { /// select quote_literal(E'C:\\Windows\\') /// ---- /// E'C:\\Windows\\' +/// +/// query T +/// select quote_literal(42.5) +/// ---- +/// '42.5' +/// +/// query T +/// select quote_literal('hello'::bytea); +/// ---- +/// E'\\x68656c6c6f' +/// +/// query T +/// select quote_literal('{"hello":"world","foo":233}'::jsonb); +/// ---- +/// '{"foo": 233, "hello": "world"}' /// ``` #[function("quote_literal(varchar) -> varchar")] pub fn quote_literal(s: &str, writer: &mut impl Write) { @@ -513,74 +527,6 @@ pub fn quote_literal(s: &str, writer: &mut impl Write) { write!(writer, "'").unwrap(); } -/// `quote_literal(value anyelement)` -/// -/// Coerce the given value to text and then quote it as a literal. -/// Embedded single-quotes and backslashes are properly doubled. -/// -/// # Example -/// -/// ```slt -/// select quote_literal(42.5) -/// ---- -/// 42.5 -/// -/// select quote_literal('hello'::bytea); -/// -------------- -/// E'\\x68656c6c6f' -/// -/// select quote_literal('{"hello":"world","foo":233}'::jsonb); -/// ---- -/// '{"foo": 233, "hello": "world"}' -/// ``` -#[function("quote_literal(any) -> varchar")] -pub fn quote_literal_any(v: ScalarRefImpl<'_>, writer: &mut impl Write) -> Result<()> { - use risingwave_common::types::ToText; - match v { - ScalarRefImpl::Utf8(s) => quote_literal(s, writer), - ScalarRefImpl::Bytea(s) => { - // `bytea` hex format - write!(writer, "E'\\\\x").unwrap(); - for b in s.iter() { - write!(writer, "{:02x}", b).unwrap(); - } - write!(writer, "'").unwrap(); - } - // Types that can be directly written without quoting - ScalarRefImpl::Int16(_) - | ScalarRefImpl::Int32(_) - | ScalarRefImpl::Int64(_) - | ScalarRefImpl::Int256(_) - | ScalarRefImpl::Float32(_) - | ScalarRefImpl::Float64(_) - | ScalarRefImpl::Decimal(_) - | ScalarRefImpl::Bool(_) - | ScalarRefImpl::Serial(_) => { - v.write(writer).map_err(|e| ExprError::InvalidParam { - name: "value", - reason: e.to_report_string().into(), - })?; - } - // Types that need to be quoted - ScalarRefImpl::Date(_) - | ScalarRefImpl::Time(_) - | ScalarRefImpl::Timestamp(_) - | ScalarRefImpl::Timestamptz(_) - | ScalarRefImpl::Interval(_) - | ScalarRefImpl::List(_) - | ScalarRefImpl::Jsonb(_) => { - quote_literal(v.to_text().as_str(), writer); - } - ScalarRefImpl::Struct(_) => { - return Err(ExprError::InvalidParam { - name: "value", - reason: "quote_literal does not support struct".into(), - }) - } - } - Ok(()) -} - /// `quote_nullable(string text)` /// /// Return the given string suitably quoted to be used as a string literal in an SQL statement @@ -594,19 +540,6 @@ pub fn quote_nullable(s: Option<&str>, writer: &mut impl Write) { } } -/// `quote_nullable(value anyelement)` -/// -/// Coerce the given value to text and then quote it as a literal; or, if the argument is null, -/// return NULL. Embedded single-quotes and backslashes are properly doubled. -#[function("quote_nullable(any) -> varchar")] -pub fn quote_nullable_any(v: Option>, writer: &mut impl Write) -> Result<()> { - match v { - Some(v) => quote_literal_any(v, writer)?, - None => write!(writer, "NULL").unwrap(), - } - Ok(()) -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/frontend/src/binder/expr/function.rs b/src/frontend/src/binder/expr/function.rs index c46ad494c899f..14d6a85acb635 100644 --- a/src/frontend/src/binder/expr/function.rs +++ b/src/frontend/src/binder/expr/function.rs @@ -40,8 +40,8 @@ use crate::binder::bind_context::Clause; use crate::binder::{Binder, UdfContext}; use crate::error::{ErrorCode, Result, RwError}; use crate::expr::{ - AggCall, Expr, ExprImpl, ExprType, FunctionCall, FunctionCallWithLambda, Literal, Now, OrderBy, - TableFunction, TableFunctionType, UserDefinedFunction, WindowFunction, + AggCall, CastContext, Expr, ExprImpl, ExprType, FunctionCall, FunctionCallWithLambda, Literal, + Now, OrderBy, TableFunction, TableFunctionType, UserDefinedFunction, WindowFunction, }; use crate::utils::Condition; @@ -1012,8 +1012,26 @@ impl Binder { ("to_ascii", raw_call(ExprType::ToAscii)), ("to_hex", raw_call(ExprType::ToHex)), ("quote_ident", raw_call(ExprType::QuoteIdent)), - ("quote_literal", raw_call(ExprType::QuoteLiteral)), - ("quote_nullable", raw_call(ExprType::QuoteNullable)), + ("quote_literal", guard_by_len(1, raw(|_binder, mut inputs| { + if inputs[0].return_type() != DataType::Varchar { + // Support `quote_literal(any)` by converting it to `quote_literal(any::text)` + // Ref. https://github.com/postgres/postgres/blob/REL_16_1/src/include/catalog/pg_proc.dat#L4641 + FunctionCall::cast_mut(&mut inputs[0], DataType::Varchar, CastContext::Explicit).map_err(|e| { + ErrorCode::BindError(format!("{} in `quote_literal`", e.as_report())) + })?; + } + Ok(FunctionCall::new_unchecked(ExprType::QuoteLiteral, inputs, DataType::Varchar).into()) + }))), + ("quote_nullable", guard_by_len(1, raw(|_binder, mut inputs| { + if inputs[0].return_type() != DataType::Varchar { + // Support `quote_nullable(any)` by converting it to `quote_nullable(any::text)` + // Ref. https://github.com/postgres/postgres/blob/REL_16_1/src/include/catalog/pg_proc.dat#L4650 + FunctionCall::cast_mut(&mut inputs[0], DataType::Varchar, CastContext::Explicit).map_err(|e| { + ErrorCode::BindError(format!("{} in `quote_nullable`", e.as_report())) + })?; + } + Ok(FunctionCall::new_unchecked(ExprType::QuoteNullable, inputs, DataType::Varchar).into()) + }))), ("string_to_array", raw_call(ExprType::StringToArray)), ("encode", raw_call(ExprType::Encode)), ("decode", raw_call(ExprType::Decode)), diff --git a/src/tests/regress/data/sql/text.sql b/src/tests/regress/data/sql/text.sql index d9e7af6030921..62372488a651b 100644 --- a/src/tests/regress/data/sql/text.sql +++ b/src/tests/regress/data/sql/text.sql @@ -43,9 +43,9 @@ select concat_ws('',10,20,null,30); select concat_ws(NULL,10,20,null,30) is null; select reverse('abcde'); select i, left('ahoj', i), right('ahoj', i) from generate_series(-5, 5) t(i) order by i; ---@ select quote_literal(''); ---@ select quote_literal('abc'''); ---@ select quote_literal(e'\\'); +select quote_literal(''); +select quote_literal('abc'''); +select quote_literal(e'\\'); -- check variadic labeled argument --@ select concat(variadic array[1,2,3]); --@ select concat_ws(',', variadic array[1,2,3]);