From 2732de1baeb666d74f794b2a0bb9af6af8dc82c0 Mon Sep 17 00:00:00 2001 From: rahuld109 Date: Mon, 23 Mar 2026 22:49:38 +0530 Subject: [PATCH 1/8] feat: add like_expr/not_like_expr methods accepting Into Add like_expr(), not_like_expr() to ExprTrait and ilike_expr(), not_ilike_expr() to PgExpr, allowing column references, function calls, and other expressions as LIKE patterns. The existing like()/not_like()/ilike()/not_ilike() methods remain unchanged for backward compatibility. The new _expr variants accept Into and delegate to binary(), matching the pattern used by eq(), gt(), and other comparison operators. Note: the _expr variants do not support the ESCAPE clause since the right-hand side is an arbitrary expression, not a LikeExpr. Closes #464 --- src/expr/trait.rs | 104 ++++++++++++++++++++++++++++ src/extension/postgres/expr.rs | 77 +++++++++++++++++++++ tests/mysql/query.rs | 54 +++++++++++++++ tests/postgres/query.rs | 122 +++++++++++++++++++++++++++++++++ tests/sqlite/query.rs | 54 +++++++++++++++ 5 files changed, 411 insertions(+) diff --git a/src/expr/trait.rs b/src/expr/trait.rs index 48ebe0a99..44f85d8d5 100644 --- a/src/expr/trait.rs +++ b/src/expr/trait.rs @@ -927,6 +927,73 @@ pub trait ExprTrait: Sized { self.binary(BinOper::Like, like.into_like_expr()) } + /// Express a `LIKE` expression with an expression as the pattern. + /// + /// This allows using column references, function calls, and other expressions + /// as LIKE patterns, not just string literals. + /// + /// Note: unlike [`like`](ExprTrait::like), this method does not support the + /// `ESCAPE` clause since the right-hand side is an arbitrary expression. + /// + /// # Examples + /// + /// ``` + /// use sea_query::{tests_cfg::*, *}; + /// + /// let query = Query::select() + /// .columns([Char::Character]) + /// .from(Char::Table) + /// .and_where(Expr::col(Char::Character).like_expr(Expr::col(Char::FontId))) + /// .to_owned(); + /// + /// assert_eq!( + /// query.to_string(MysqlQueryBuilder), + /// r#"SELECT `character` FROM `character` WHERE `character` LIKE `font_id`"# + /// ); + /// assert_eq!( + /// query.to_string(PostgresQueryBuilder), + /// r#"SELECT "character" FROM "character" WHERE "character" LIKE "font_id""# + /// ); + /// assert_eq!( + /// query.to_string(SqliteQueryBuilder), + /// r#"SELECT "character" FROM "character" WHERE "character" LIKE "font_id""# + /// ); + /// ``` + /// + /// Like with function expression + /// + /// ``` + /// use sea_query::{tests_cfg::*, *}; + /// + /// let query = Query::select() + /// .columns([Char::Character]) + /// .from(Char::Table) + /// .and_where( + /// Expr::expr(Func::lower(Expr::col(Char::Character))) + /// .like_expr(Func::lower(Expr::col(Char::FontId))) + /// ) + /// .to_owned(); + /// + /// assert_eq!( + /// query.to_string(MysqlQueryBuilder), + /// r#"SELECT `character` FROM `character` WHERE LOWER(`character`) LIKE LOWER(`font_id`)"# + /// ); + /// assert_eq!( + /// query.to_string(PostgresQueryBuilder), + /// r#"SELECT "character" FROM "character" WHERE LOWER("character") LIKE LOWER("font_id")"# + /// ); + /// assert_eq!( + /// query.to_string(SqliteQueryBuilder), + /// r#"SELECT "character" FROM "character" WHERE LOWER("character") LIKE LOWER("font_id")"# + /// ); + /// ``` + fn like_expr(self, right: R) -> Expr + where + R: Into, + { + self.binary(BinOper::Like, right) + } + /// Express a less than (`<`) expression. /// /// # Examples @@ -1262,6 +1329,43 @@ pub trait ExprTrait: Sized { self.binary(BinOper::NotLike, like.into_like_expr()) } + /// Express a `NOT LIKE` expression with an expression as the pattern. + /// + /// Unlike [`not_like`](ExprTrait::not_like), this method does not support the + /// `ESCAPE` clause since the right-hand side is an arbitrary expression. + /// See [`like_expr`](ExprTrait::like_expr) for more details. + /// + /// # Examples + /// + /// ``` + /// use sea_query::{tests_cfg::*, *}; + /// + /// let query = Query::select() + /// .columns([Char::Character]) + /// .from(Char::Table) + /// .and_where(Expr::col(Char::Character).not_like_expr(Expr::col(Char::FontId))) + /// .to_owned(); + /// + /// assert_eq!( + /// query.to_string(MysqlQueryBuilder), + /// r#"SELECT `character` FROM `character` WHERE `character` NOT LIKE `font_id`"# + /// ); + /// assert_eq!( + /// query.to_string(PostgresQueryBuilder), + /// r#"SELECT "character" FROM "character" WHERE "character" NOT LIKE "font_id""# + /// ); + /// assert_eq!( + /// query.to_string(SqliteQueryBuilder), + /// r#"SELECT "character" FROM "character" WHERE "character" NOT LIKE "font_id""# + /// ); + /// ``` + fn not_like_expr(self, right: R) -> Expr + where + R: Into, + { + self.binary(BinOper::NotLike, right) + } + /// Express a logical `OR` operation. /// /// # Examples diff --git a/src/extension/postgres/expr.rs b/src/extension/postgres/expr.rs index 3f57e6961..b03fd4b6c 100644 --- a/src/extension/postgres/expr.rs +++ b/src/extension/postgres/expr.rs @@ -147,6 +147,54 @@ pub trait PgExpr: ExprTrait { self.binary(PgBinOper::ILike, like.into_like_expr()) } + /// Express an `ILIKE` expression with an expression as the pattern. + /// + /// Unlike [`ilike`](PgExpr::ilike), this method does not support the + /// `ESCAPE` clause since the right-hand side is an arbitrary expression. + /// + /// # Examples + /// + /// ``` + /// use sea_query::{extension::postgres::PgExpr, tests_cfg::*, *}; + /// + /// let query = Query::select() + /// .columns([Char::Character]) + /// .from(Char::Table) + /// .and_where(Expr::col(Char::Character).ilike_expr(Expr::col(Char::FontId))) + /// .to_owned(); + /// + /// assert_eq!( + /// query.to_string(PostgresQueryBuilder), + /// r#"SELECT "character" FROM "character" WHERE "character" ILIKE "font_id""# + /// ); + /// ``` + /// + /// ILIKE with concatenated pattern + /// + /// ``` + /// use sea_query::{extension::postgres::PgExpr, tests_cfg::*, *}; + /// + /// let query = Query::select() + /// .columns([Char::Character]) + /// .from(Char::Table) + /// .and_where( + /// Expr::col(Char::Character) + /// .ilike_expr(Expr::val("%").concat(Expr::col(Char::FontId))) + /// ) + /// .to_owned(); + /// + /// assert_eq!( + /// query.to_string(PostgresQueryBuilder), + /// r#"SELECT "character" FROM "character" WHERE "character" ILIKE ('%' || "font_id")"# + /// ); + /// ``` + fn ilike_expr(self, right: R) -> Expr + where + R: Into, + { + self.binary(PgBinOper::ILike, right) + } + /// Express a `NOT ILIKE` expression fn not_ilike(self, like: L) -> Expr where @@ -155,6 +203,35 @@ pub trait PgExpr: ExprTrait { self.binary(PgBinOper::NotILike, like.into_like_expr()) } + /// Express a `NOT ILIKE` expression with an expression as the pattern. + /// + /// Unlike [`not_ilike`](PgExpr::not_ilike), this method does not support the + /// `ESCAPE` clause since the right-hand side is an arbitrary expression. + /// See [`ilike_expr`](PgExpr::ilike_expr) for more details. + /// + /// # Examples + /// + /// ``` + /// use sea_query::{extension::postgres::PgExpr, tests_cfg::*, *}; + /// + /// let query = Query::select() + /// .columns([Char::Character]) + /// .from(Char::Table) + /// .and_where(Expr::col(Char::Character).not_ilike_expr(Expr::col(Char::FontId))) + /// .to_owned(); + /// + /// assert_eq!( + /// query.to_string(PostgresQueryBuilder), + /// r#"SELECT "character" FROM "character" WHERE "character" NOT ILIKE "font_id""# + /// ); + /// ``` + fn not_ilike_expr(self, right: R) -> Expr + where + R: Into, + { + self.binary(PgBinOper::NotILike, right) + } + /// Express a postgres retrieves JSON field as JSON value (`->`). /// /// # Examples diff --git a/tests/mysql/query.rs b/tests/mysql/query.rs index 98276317d..58b1e6c22 100644 --- a/tests/mysql/query.rs +++ b/tests/mysql/query.rs @@ -1553,3 +1553,57 @@ fn sub_query_with_fn() { "SELECT ARRAY((SELECT * FROM `character`))" ); } + +#[test] +fn select_like_expr_column() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where(Expr::col(Char::Character).like_expr(Expr::col(Char::FontId))) + .to_string(MysqlQueryBuilder), + r#"SELECT `character` FROM `character` WHERE `character` LIKE `font_id`"# + ); +} + +#[test] +fn select_like_expr_function() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where( + Expr::expr(Func::lower(Expr::col(Char::Character))) + .like_expr(Func::lower(Expr::col(Char::FontId))) + ) + .to_string(MysqlQueryBuilder), + r#"SELECT `character` FROM `character` WHERE LOWER(`character`) LIKE LOWER(`font_id`)"# + ); +} + +#[test] +fn select_not_like_expr_column() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where(Expr::col(Char::Character).not_like_expr(Expr::col(Char::FontId))) + .to_string(MysqlQueryBuilder), + r#"SELECT `character` FROM `character` WHERE `character` NOT LIKE `font_id`"# + ); +} + +#[test] +fn select_not_like_expr_function() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where( + Expr::expr(Func::lower(Expr::col(Char::Character))) + .not_like_expr(Func::lower(Expr::col(Char::FontId))) + ) + .to_string(MysqlQueryBuilder), + r#"SELECT `character` FROM `character` WHERE LOWER(`character`) NOT LIKE LOWER(`font_id`)"# + ); +} diff --git a/tests/postgres/query.rs b/tests/postgres/query.rs index 06dd18c48..aacf9c37f 100644 --- a/tests/postgres/query.rs +++ b/tests/postgres/query.rs @@ -2,6 +2,7 @@ use core::f64; use super::*; use pretty_assertions::assert_eq; +use sea_query::extension::postgres::PgExpr; use sea_query::{audit::AuditTrait, extension::postgres::PgBinOper}; #[test] @@ -2525,3 +2526,124 @@ fn test_pgvector_select() { r#"SELECT "character" FROM "character" WHERE "character" = '[1,2]'"# ); } + +#[test] +fn select_like_expr_column() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where(Expr::col(Char::Character).like_expr(Expr::col(Char::FontId))) + .to_string(PostgresQueryBuilder), + r#"SELECT "character" FROM "character" WHERE "character" LIKE "font_id""# + ); +} + +#[test] +fn select_like_expr_function() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where( + Expr::expr(Func::lower(Expr::col(Char::Character))) + .like_expr(Func::lower(Expr::col(Char::FontId))) + ) + .to_string(PostgresQueryBuilder), + r#"SELECT "character" FROM "character" WHERE LOWER("character") LIKE LOWER("font_id")"# + ); +} + +#[test] +fn select_like_expr_string() { + // Verify string literals work through the Into path + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where(Expr::col(Char::Character).like_expr("A%")) + .to_string(PostgresQueryBuilder), + r#"SELECT "character" FROM "character" WHERE "character" LIKE 'A%'"# + ); +} + +#[test] +fn select_not_like_expr_column() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where(Expr::col(Char::Character).not_like_expr(Expr::col(Char::FontId))) + .to_string(PostgresQueryBuilder), + r#"SELECT "character" FROM "character" WHERE "character" NOT LIKE "font_id""# + ); +} + +#[test] +fn select_not_like_expr_function() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where( + Expr::expr(Func::lower(Expr::col(Char::Character))) + .not_like_expr(Func::lower(Expr::col(Char::FontId))) + ) + .to_string(PostgresQueryBuilder), + r#"SELECT "character" FROM "character" WHERE LOWER("character") NOT LIKE LOWER("font_id")"# + ); +} + +#[test] +fn select_ilike_expr_column() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where(Expr::col(Char::Character).ilike_expr(Expr::col(Char::FontId))) + .to_string(PostgresQueryBuilder), + r#"SELECT "character" FROM "character" WHERE "character" ILIKE "font_id""# + ); +} + +#[test] +fn select_ilike_expr_concat() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where( + Expr::col(Char::Character) + .ilike_expr(Expr::val("%").concat(Expr::col(Char::FontId))) + ) + .to_string(PostgresQueryBuilder), + r#"SELECT "character" FROM "character" WHERE "character" ILIKE ('%' || "font_id")"# + ); +} + +#[test] +fn select_not_ilike_expr_column() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where(Expr::col(Char::Character).not_ilike_expr(Expr::col(Char::FontId))) + .to_string(PostgresQueryBuilder), + r#"SELECT "character" FROM "character" WHERE "character" NOT ILIKE "font_id""# + ); +} + +#[test] +fn select_not_ilike_expr_concat() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where( + Expr::col(Char::Character) + .not_ilike_expr(Expr::val("%").concat(Expr::col(Char::FontId))) + ) + .to_string(PostgresQueryBuilder), + r#"SELECT "character" FROM "character" WHERE "character" NOT ILIKE ('%' || "font_id")"# + ); +} diff --git a/tests/sqlite/query.rs b/tests/sqlite/query.rs index 54a5008be..6b1c90b8e 100644 --- a/tests/sqlite/query.rs +++ b/tests/sqlite/query.rs @@ -1859,3 +1859,57 @@ fn recursive_with_multiple_ctes() { r#"WITH RECURSIVE "sub1" ("a") AS (SELECT * FROM "character") , "sub2" ("b") AS (SELECT * FROM "character") SELECT * FROM "sub1" UNION ALL SELECT * FROM "sub2""# ); } + +#[test] +fn select_like_expr_column() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where(Expr::col(Char::Character).like_expr(Expr::col(Char::FontId))) + .to_string(SqliteQueryBuilder), + r#"SELECT "character" FROM "character" WHERE "character" LIKE "font_id""# + ); +} + +#[test] +fn select_like_expr_function() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where( + Expr::expr(Func::lower(Expr::col(Char::Character))) + .like_expr(Func::lower(Expr::col(Char::FontId))) + ) + .to_string(SqliteQueryBuilder), + r#"SELECT "character" FROM "character" WHERE LOWER("character") LIKE LOWER("font_id")"# + ); +} + +#[test] +fn select_not_like_expr_column() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where(Expr::col(Char::Character).not_like_expr(Expr::col(Char::FontId))) + .to_string(SqliteQueryBuilder), + r#"SELECT "character" FROM "character" WHERE "character" NOT LIKE "font_id""# + ); +} + +#[test] +fn select_not_like_expr_function() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where( + Expr::expr(Func::lower(Expr::col(Char::Character))) + .not_like_expr(Func::lower(Expr::col(Char::FontId))) + ) + .to_string(SqliteQueryBuilder), + r#"SELECT "character" FROM "character" WHERE LOWER("character") NOT LIKE LOWER("font_id")"# + ); +} From 1fe80865c2107f1d81c5a93f8c4bd1e95403d626 Mon Sep 17 00:00:00 2001 From: rahuld109 Date: Tue, 24 Mar 2026 03:10:45 +0530 Subject: [PATCH 2/8] refactor: turn LikeExpr into enum to accept Expr directly Refactor LikeExpr to hold either a String or an Expr internally, and add From and From impls. This allows passing expressions directly to like()/not_like()/ilike()/not_ilike() without needing separate methods. Removes the like_expr/not_like_expr/ilike_expr/not_ilike_expr methods from the previous commit in favor of this simpler approach. --- src/expr/enum.rs | 8 ++- src/expr/trait.rs | 104 --------------------------------- src/extension/postgres/expr.rs | 77 ------------------------ src/types/mod.rs | 35 ++++++++--- tests/mysql/query.rs | 27 ++------- tests/postgres/query.rs | 78 ++++--------------------- tests/sqlite/query.rs | 27 ++------- 7 files changed, 56 insertions(+), 300 deletions(-) diff --git a/src/expr/enum.rs b/src/expr/enum.rs index 1f85792a3..37fe9a8e5 100644 --- a/src/expr/enum.rs +++ b/src/expr/enum.rs @@ -868,13 +868,17 @@ impl From for Expr { impl From for Expr { fn from(like: LikeExpr) -> Self { + let base: Expr = match like.inner { + crate::LikeExprInner::Str(s) => s.into(), + crate::LikeExprInner::Expr(e) => e, + }; match like.escape { Some(escape) => Self::Binary( - Box::new(like.pattern.into()), + Box::new(base), BinOper::Escape, Box::new(Expr::Constant(escape.into())), ), - None => like.pattern.into(), + None => base, } } } diff --git a/src/expr/trait.rs b/src/expr/trait.rs index 44f85d8d5..48ebe0a99 100644 --- a/src/expr/trait.rs +++ b/src/expr/trait.rs @@ -927,73 +927,6 @@ pub trait ExprTrait: Sized { self.binary(BinOper::Like, like.into_like_expr()) } - /// Express a `LIKE` expression with an expression as the pattern. - /// - /// This allows using column references, function calls, and other expressions - /// as LIKE patterns, not just string literals. - /// - /// Note: unlike [`like`](ExprTrait::like), this method does not support the - /// `ESCAPE` clause since the right-hand side is an arbitrary expression. - /// - /// # Examples - /// - /// ``` - /// use sea_query::{tests_cfg::*, *}; - /// - /// let query = Query::select() - /// .columns([Char::Character]) - /// .from(Char::Table) - /// .and_where(Expr::col(Char::Character).like_expr(Expr::col(Char::FontId))) - /// .to_owned(); - /// - /// assert_eq!( - /// query.to_string(MysqlQueryBuilder), - /// r#"SELECT `character` FROM `character` WHERE `character` LIKE `font_id`"# - /// ); - /// assert_eq!( - /// query.to_string(PostgresQueryBuilder), - /// r#"SELECT "character" FROM "character" WHERE "character" LIKE "font_id""# - /// ); - /// assert_eq!( - /// query.to_string(SqliteQueryBuilder), - /// r#"SELECT "character" FROM "character" WHERE "character" LIKE "font_id""# - /// ); - /// ``` - /// - /// Like with function expression - /// - /// ``` - /// use sea_query::{tests_cfg::*, *}; - /// - /// let query = Query::select() - /// .columns([Char::Character]) - /// .from(Char::Table) - /// .and_where( - /// Expr::expr(Func::lower(Expr::col(Char::Character))) - /// .like_expr(Func::lower(Expr::col(Char::FontId))) - /// ) - /// .to_owned(); - /// - /// assert_eq!( - /// query.to_string(MysqlQueryBuilder), - /// r#"SELECT `character` FROM `character` WHERE LOWER(`character`) LIKE LOWER(`font_id`)"# - /// ); - /// assert_eq!( - /// query.to_string(PostgresQueryBuilder), - /// r#"SELECT "character" FROM "character" WHERE LOWER("character") LIKE LOWER("font_id")"# - /// ); - /// assert_eq!( - /// query.to_string(SqliteQueryBuilder), - /// r#"SELECT "character" FROM "character" WHERE LOWER("character") LIKE LOWER("font_id")"# - /// ); - /// ``` - fn like_expr(self, right: R) -> Expr - where - R: Into, - { - self.binary(BinOper::Like, right) - } - /// Express a less than (`<`) expression. /// /// # Examples @@ -1329,43 +1262,6 @@ pub trait ExprTrait: Sized { self.binary(BinOper::NotLike, like.into_like_expr()) } - /// Express a `NOT LIKE` expression with an expression as the pattern. - /// - /// Unlike [`not_like`](ExprTrait::not_like), this method does not support the - /// `ESCAPE` clause since the right-hand side is an arbitrary expression. - /// See [`like_expr`](ExprTrait::like_expr) for more details. - /// - /// # Examples - /// - /// ``` - /// use sea_query::{tests_cfg::*, *}; - /// - /// let query = Query::select() - /// .columns([Char::Character]) - /// .from(Char::Table) - /// .and_where(Expr::col(Char::Character).not_like_expr(Expr::col(Char::FontId))) - /// .to_owned(); - /// - /// assert_eq!( - /// query.to_string(MysqlQueryBuilder), - /// r#"SELECT `character` FROM `character` WHERE `character` NOT LIKE `font_id`"# - /// ); - /// assert_eq!( - /// query.to_string(PostgresQueryBuilder), - /// r#"SELECT "character" FROM "character" WHERE "character" NOT LIKE "font_id""# - /// ); - /// assert_eq!( - /// query.to_string(SqliteQueryBuilder), - /// r#"SELECT "character" FROM "character" WHERE "character" NOT LIKE "font_id""# - /// ); - /// ``` - fn not_like_expr(self, right: R) -> Expr - where - R: Into, - { - self.binary(BinOper::NotLike, right) - } - /// Express a logical `OR` operation. /// /// # Examples diff --git a/src/extension/postgres/expr.rs b/src/extension/postgres/expr.rs index b03fd4b6c..3f57e6961 100644 --- a/src/extension/postgres/expr.rs +++ b/src/extension/postgres/expr.rs @@ -147,54 +147,6 @@ pub trait PgExpr: ExprTrait { self.binary(PgBinOper::ILike, like.into_like_expr()) } - /// Express an `ILIKE` expression with an expression as the pattern. - /// - /// Unlike [`ilike`](PgExpr::ilike), this method does not support the - /// `ESCAPE` clause since the right-hand side is an arbitrary expression. - /// - /// # Examples - /// - /// ``` - /// use sea_query::{extension::postgres::PgExpr, tests_cfg::*, *}; - /// - /// let query = Query::select() - /// .columns([Char::Character]) - /// .from(Char::Table) - /// .and_where(Expr::col(Char::Character).ilike_expr(Expr::col(Char::FontId))) - /// .to_owned(); - /// - /// assert_eq!( - /// query.to_string(PostgresQueryBuilder), - /// r#"SELECT "character" FROM "character" WHERE "character" ILIKE "font_id""# - /// ); - /// ``` - /// - /// ILIKE with concatenated pattern - /// - /// ``` - /// use sea_query::{extension::postgres::PgExpr, tests_cfg::*, *}; - /// - /// let query = Query::select() - /// .columns([Char::Character]) - /// .from(Char::Table) - /// .and_where( - /// Expr::col(Char::Character) - /// .ilike_expr(Expr::val("%").concat(Expr::col(Char::FontId))) - /// ) - /// .to_owned(); - /// - /// assert_eq!( - /// query.to_string(PostgresQueryBuilder), - /// r#"SELECT "character" FROM "character" WHERE "character" ILIKE ('%' || "font_id")"# - /// ); - /// ``` - fn ilike_expr(self, right: R) -> Expr - where - R: Into, - { - self.binary(PgBinOper::ILike, right) - } - /// Express a `NOT ILIKE` expression fn not_ilike(self, like: L) -> Expr where @@ -203,35 +155,6 @@ pub trait PgExpr: ExprTrait { self.binary(PgBinOper::NotILike, like.into_like_expr()) } - /// Express a `NOT ILIKE` expression with an expression as the pattern. - /// - /// Unlike [`not_ilike`](PgExpr::not_ilike), this method does not support the - /// `ESCAPE` clause since the right-hand side is an arbitrary expression. - /// See [`ilike_expr`](PgExpr::ilike_expr) for more details. - /// - /// # Examples - /// - /// ``` - /// use sea_query::{extension::postgres::PgExpr, tests_cfg::*, *}; - /// - /// let query = Query::select() - /// .columns([Char::Character]) - /// .from(Char::Table) - /// .and_where(Expr::col(Char::Character).not_ilike_expr(Expr::col(Char::FontId))) - /// .to_owned(); - /// - /// assert_eq!( - /// query.to_string(PostgresQueryBuilder), - /// r#"SELECT "character" FROM "character" WHERE "character" NOT ILIKE "font_id""# - /// ); - /// ``` - fn not_ilike_expr(self, right: R) -> Expr - where - R: Into, - { - self.binary(PgBinOper::NotILike, right) - } - /// Express a postgres retrieves JSON field as JSON value (`->`). /// /// # Examples diff --git a/src/types/mod.rs b/src/types/mod.rs index 3c02d6a1a..3e42f913b 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -172,17 +172,23 @@ pub enum Keyword { /// Like Expression #[derive(Debug, Clone)] pub struct LikeExpr { - pub(crate) pattern: String, + pub(crate) inner: LikeExprInner, pub(crate) escape: Option, } +#[derive(Debug, Clone)] +pub(crate) enum LikeExprInner { + Str(String), + Expr(crate::Expr), +} + impl LikeExpr { pub fn new(pattern: T) -> Self where T: Into, { Self { - pattern: pattern.into(), + inner: LikeExprInner::Str(pattern.into()), escape: None, } } @@ -192,16 +198,13 @@ impl LikeExpr { where T: Into, { - Self { - pattern: pattern.into(), - escape: None, - } + LikeExpr::new(pattern) } pub fn escape(self, c: char) -> Self { Self { - pattern: self.pattern, escape: Some(c), + ..self } } } @@ -228,6 +231,24 @@ where } } +impl From for LikeExpr { + fn from(expr: crate::Expr) -> Self { + Self { + inner: LikeExprInner::Expr(expr), + escape: None, + } + } +} + +impl From for LikeExpr { + fn from(func: crate::FunctionCall) -> Self { + Self { + inner: LikeExprInner::Expr(crate::Expr::FunctionCall(func)), + escape: None, + } + } +} + /// SubQuery operators #[derive(Debug, Copy, Clone, PartialEq)] #[non_exhaustive] diff --git a/tests/mysql/query.rs b/tests/mysql/query.rs index 58b1e6c22..11470c9e5 100644 --- a/tests/mysql/query.rs +++ b/tests/mysql/query.rs @@ -1555,26 +1555,26 @@ fn sub_query_with_fn() { } #[test] -fn select_like_expr_column() { +fn select_like_with_expr() { assert_eq!( Query::select() .column(Char::Character) .from(Char::Table) - .and_where(Expr::col(Char::Character).like_expr(Expr::col(Char::FontId))) + .and_where(Expr::col(Char::Character).like(Expr::col(Char::FontId))) .to_string(MysqlQueryBuilder), r#"SELECT `character` FROM `character` WHERE `character` LIKE `font_id`"# ); } #[test] -fn select_like_expr_function() { +fn select_like_with_function() { assert_eq!( Query::select() .column(Char::Character) .from(Char::Table) .and_where( Expr::expr(Func::lower(Expr::col(Char::Character))) - .like_expr(Func::lower(Expr::col(Char::FontId))) + .like(Func::lower(Expr::col(Char::FontId))) ) .to_string(MysqlQueryBuilder), r#"SELECT `character` FROM `character` WHERE LOWER(`character`) LIKE LOWER(`font_id`)"# @@ -1582,28 +1582,13 @@ fn select_like_expr_function() { } #[test] -fn select_not_like_expr_column() { +fn select_not_like_with_expr() { assert_eq!( Query::select() .column(Char::Character) .from(Char::Table) - .and_where(Expr::col(Char::Character).not_like_expr(Expr::col(Char::FontId))) + .and_where(Expr::col(Char::Character).not_like(Expr::col(Char::FontId))) .to_string(MysqlQueryBuilder), r#"SELECT `character` FROM `character` WHERE `character` NOT LIKE `font_id`"# ); } - -#[test] -fn select_not_like_expr_function() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where( - Expr::expr(Func::lower(Expr::col(Char::Character))) - .not_like_expr(Func::lower(Expr::col(Char::FontId))) - ) - .to_string(MysqlQueryBuilder), - r#"SELECT `character` FROM `character` WHERE LOWER(`character`) NOT LIKE LOWER(`font_id`)"# - ); -} diff --git a/tests/postgres/query.rs b/tests/postgres/query.rs index aacf9c37f..e99470c11 100644 --- a/tests/postgres/query.rs +++ b/tests/postgres/query.rs @@ -2528,26 +2528,26 @@ fn test_pgvector_select() { } #[test] -fn select_like_expr_column() { +fn select_like_with_expr() { assert_eq!( Query::select() .column(Char::Character) .from(Char::Table) - .and_where(Expr::col(Char::Character).like_expr(Expr::col(Char::FontId))) + .and_where(Expr::col(Char::Character).like(Expr::col(Char::FontId))) .to_string(PostgresQueryBuilder), r#"SELECT "character" FROM "character" WHERE "character" LIKE "font_id""# ); } #[test] -fn select_like_expr_function() { +fn select_like_with_function() { assert_eq!( Query::select() .column(Char::Character) .from(Char::Table) .and_where( Expr::expr(Func::lower(Expr::col(Char::Character))) - .like_expr(Func::lower(Expr::col(Char::FontId))) + .like(Func::lower(Expr::col(Char::FontId))) ) .to_string(PostgresQueryBuilder), r#"SELECT "character" FROM "character" WHERE LOWER("character") LIKE LOWER("font_id")"# @@ -2555,95 +2555,37 @@ fn select_like_expr_function() { } #[test] -fn select_like_expr_string() { - // Verify string literals work through the Into path +fn select_not_like_with_expr() { assert_eq!( Query::select() .column(Char::Character) .from(Char::Table) - .and_where(Expr::col(Char::Character).like_expr("A%")) - .to_string(PostgresQueryBuilder), - r#"SELECT "character" FROM "character" WHERE "character" LIKE 'A%'"# - ); -} - -#[test] -fn select_not_like_expr_column() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where(Expr::col(Char::Character).not_like_expr(Expr::col(Char::FontId))) + .and_where(Expr::col(Char::Character).not_like(Expr::col(Char::FontId))) .to_string(PostgresQueryBuilder), r#"SELECT "character" FROM "character" WHERE "character" NOT LIKE "font_id""# ); } #[test] -fn select_not_like_expr_function() { +fn select_ilike_with_expr() { assert_eq!( Query::select() .column(Char::Character) .from(Char::Table) - .and_where( - Expr::expr(Func::lower(Expr::col(Char::Character))) - .not_like_expr(Func::lower(Expr::col(Char::FontId))) - ) - .to_string(PostgresQueryBuilder), - r#"SELECT "character" FROM "character" WHERE LOWER("character") NOT LIKE LOWER("font_id")"# - ); -} - -#[test] -fn select_ilike_expr_column() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where(Expr::col(Char::Character).ilike_expr(Expr::col(Char::FontId))) + .and_where(Expr::col(Char::Character).ilike(Expr::col(Char::FontId))) .to_string(PostgresQueryBuilder), r#"SELECT "character" FROM "character" WHERE "character" ILIKE "font_id""# ); } #[test] -fn select_ilike_expr_concat() { +fn select_not_ilike_with_expr() { assert_eq!( Query::select() .column(Char::Character) .from(Char::Table) - .and_where( - Expr::col(Char::Character) - .ilike_expr(Expr::val("%").concat(Expr::col(Char::FontId))) - ) - .to_string(PostgresQueryBuilder), - r#"SELECT "character" FROM "character" WHERE "character" ILIKE ('%' || "font_id")"# - ); -} - -#[test] -fn select_not_ilike_expr_column() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where(Expr::col(Char::Character).not_ilike_expr(Expr::col(Char::FontId))) + .and_where(Expr::col(Char::Character).not_ilike(Expr::col(Char::FontId))) .to_string(PostgresQueryBuilder), r#"SELECT "character" FROM "character" WHERE "character" NOT ILIKE "font_id""# ); } - -#[test] -fn select_not_ilike_expr_concat() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where( - Expr::col(Char::Character) - .not_ilike_expr(Expr::val("%").concat(Expr::col(Char::FontId))) - ) - .to_string(PostgresQueryBuilder), - r#"SELECT "character" FROM "character" WHERE "character" NOT ILIKE ('%' || "font_id")"# - ); -} diff --git a/tests/sqlite/query.rs b/tests/sqlite/query.rs index 6b1c90b8e..47e0150dd 100644 --- a/tests/sqlite/query.rs +++ b/tests/sqlite/query.rs @@ -1861,26 +1861,26 @@ fn recursive_with_multiple_ctes() { } #[test] -fn select_like_expr_column() { +fn select_like_with_expr() { assert_eq!( Query::select() .column(Char::Character) .from(Char::Table) - .and_where(Expr::col(Char::Character).like_expr(Expr::col(Char::FontId))) + .and_where(Expr::col(Char::Character).like(Expr::col(Char::FontId))) .to_string(SqliteQueryBuilder), r#"SELECT "character" FROM "character" WHERE "character" LIKE "font_id""# ); } #[test] -fn select_like_expr_function() { +fn select_like_with_function() { assert_eq!( Query::select() .column(Char::Character) .from(Char::Table) .and_where( Expr::expr(Func::lower(Expr::col(Char::Character))) - .like_expr(Func::lower(Expr::col(Char::FontId))) + .like(Func::lower(Expr::col(Char::FontId))) ) .to_string(SqliteQueryBuilder), r#"SELECT "character" FROM "character" WHERE LOWER("character") LIKE LOWER("font_id")"# @@ -1888,28 +1888,13 @@ fn select_like_expr_function() { } #[test] -fn select_not_like_expr_column() { +fn select_not_like_with_expr() { assert_eq!( Query::select() .column(Char::Character) .from(Char::Table) - .and_where(Expr::col(Char::Character).not_like_expr(Expr::col(Char::FontId))) + .and_where(Expr::col(Char::Character).not_like(Expr::col(Char::FontId))) .to_string(SqliteQueryBuilder), r#"SELECT "character" FROM "character" WHERE "character" NOT LIKE "font_id""# ); } - -#[test] -fn select_not_like_expr_function() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where( - Expr::expr(Func::lower(Expr::col(Char::Character))) - .not_like_expr(Func::lower(Expr::col(Char::FontId))) - ) - .to_string(SqliteQueryBuilder), - r#"SELECT "character" FROM "character" WHERE LOWER("character") NOT LIKE LOWER("font_id")"# - ); -} From 0b1d1987323353406ba464aabd1f673dccb4f031 Mon Sep 17 00:00:00 2001 From: rahuld109 Date: Wed, 25 Mar 2026 01:56:25 +0530 Subject: [PATCH 3/8] refactor: turn LikeExpr into a public enum Per reviewer feedback, LikeExpr is now a public enum with Str and Expr variants instead of a struct wrapping a private inner enum. Escape only applies to the Str variant. --- src/expr/enum.rs | 19 +++++++++++-------- src/types/mod.rs | 41 ++++++++++++++++++++--------------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/expr/enum.rs b/src/expr/enum.rs index 37fe9a8e5..01346008b 100644 --- a/src/expr/enum.rs +++ b/src/expr/enum.rs @@ -868,17 +868,20 @@ impl From for Expr { impl From for Expr { fn from(like: LikeExpr) -> Self { - let base: Expr = match like.inner { - crate::LikeExprInner::Str(s) => s.into(), - crate::LikeExprInner::Expr(e) => e, - }; - match like.escape { - Some(escape) => Self::Binary( - Box::new(base), + match like { + LikeExpr::Str { + pattern, + escape: Some(escape), + } => Self::Binary( + Box::new(pattern.into()), BinOper::Escape, Box::new(Expr::Constant(escape.into())), ), - None => base, + LikeExpr::Str { + pattern, + escape: None, + } => pattern.into(), + LikeExpr::Expr(expr) => expr, } } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 3e42f913b..60e27ad7b 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -171,14 +171,16 @@ pub enum Keyword { /// Like Expression #[derive(Debug, Clone)] -pub struct LikeExpr { - pub(crate) inner: LikeExprInner, - pub(crate) escape: Option, -} - -#[derive(Debug, Clone)] -pub(crate) enum LikeExprInner { - Str(String), +#[non_exhaustive] +pub enum LikeExpr { + /// A string pattern with optional escape character + Str { + /// The pattern string + pattern: String, + /// Optional escape character + escape: Option, + }, + /// An arbitrary expression Expr(crate::Expr), } @@ -187,8 +189,8 @@ impl LikeExpr { where T: Into, { - Self { - inner: LikeExprInner::Str(pattern.into()), + Self::Str { + pattern: pattern.into(), escape: None, } } @@ -202,9 +204,12 @@ impl LikeExpr { } pub fn escape(self, c: char) -> Self { - Self { - escape: Some(c), - ..self + match self { + Self::Str { pattern, .. } => Self::Str { + pattern, + escape: Some(c), + }, + Self::Expr(_) => self, } } } @@ -233,19 +238,13 @@ where impl From for LikeExpr { fn from(expr: crate::Expr) -> Self { - Self { - inner: LikeExprInner::Expr(expr), - escape: None, - } + Self::Expr(expr) } } impl From for LikeExpr { fn from(func: crate::FunctionCall) -> Self { - Self { - inner: LikeExprInner::Expr(crate::Expr::FunctionCall(func)), - escape: None, - } + Self::Expr(crate::Expr::FunctionCall(func)) } } From 2e96896b8e88fc8f346453eb5c3aa44bbfd7021a Mon Sep 17 00:00:00 2001 From: rahuld109 Date: Wed, 25 Mar 2026 22:19:31 +0530 Subject: [PATCH 4/8] refactor: use newtype wrapper for LikeExpr to keep variants private Move escape into the Str variant since it only applies to string patterns. Wrap LikeExprInner in a newtype struct to keep enum variants private. --- src/expr/enum.rs | 8 ++++---- src/types/mod.rs | 31 ++++++++++++++++--------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/expr/enum.rs b/src/expr/enum.rs index 01346008b..85a24f43b 100644 --- a/src/expr/enum.rs +++ b/src/expr/enum.rs @@ -868,8 +868,8 @@ impl From for Expr { impl From for Expr { fn from(like: LikeExpr) -> Self { - match like { - LikeExpr::Str { + match like.0 { + crate::LikeExprInner::Str { pattern, escape: Some(escape), } => Self::Binary( @@ -877,11 +877,11 @@ impl From for Expr { BinOper::Escape, Box::new(Expr::Constant(escape.into())), ), - LikeExpr::Str { + crate::LikeExprInner::Str { pattern, escape: None, } => pattern.into(), - LikeExpr::Expr(expr) => expr, + crate::LikeExprInner::Expr(expr) => expr, } } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 60e27ad7b..f0520f818 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -169,18 +169,19 @@ pub enum Keyword { Custom(DynIden), } -/// Like Expression +/// Like Expression. +/// +/// Wraps [`LikeExprInner`] to keep enum variants private, since Rust enum +/// variants are always public. #[derive(Debug, Clone)] -#[non_exhaustive] -pub enum LikeExpr { - /// A string pattern with optional escape character +pub struct LikeExpr(pub(crate) LikeExprInner); + +#[derive(Debug, Clone)] +pub(crate) enum LikeExprInner { Str { - /// The pattern string pattern: String, - /// Optional escape character escape: Option, }, - /// An arbitrary expression Expr(crate::Expr), } @@ -189,10 +190,10 @@ impl LikeExpr { where T: Into, { - Self::Str { + Self(LikeExprInner::Str { pattern: pattern.into(), escape: None, - } + }) } #[deprecated(since = "0.29.0", note = "Please use the [`LikeExpr::new`] method")] @@ -204,12 +205,12 @@ impl LikeExpr { } pub fn escape(self, c: char) -> Self { - match self { - Self::Str { pattern, .. } => Self::Str { + match self.0 { + LikeExprInner::Str { pattern, .. } => Self(LikeExprInner::Str { pattern, escape: Some(c), - }, - Self::Expr(_) => self, + }), + LikeExprInner::Expr(_) => self, } } } @@ -238,13 +239,13 @@ where impl From for LikeExpr { fn from(expr: crate::Expr) -> Self { - Self::Expr(expr) + Self(LikeExprInner::Expr(expr)) } } impl From for LikeExpr { fn from(func: crate::FunctionCall) -> Self { - Self::Expr(crate::Expr::FunctionCall(func)) + Self(LikeExprInner::Expr(crate::Expr::FunctionCall(func))) } } From 27b8aba5463d9da88afbe01688286be9fcf5ac0a Mon Sep 17 00:00:00 2001 From: rahuld109 Date: Thu, 26 Mar 2026 21:35:39 +0530 Subject: [PATCH 5/8] chore: remove redundant tests --- tests/mysql/query.rs | 39 ------------------------- tests/postgres/query.rs | 64 ----------------------------------------- tests/sqlite/query.rs | 39 ------------------------- 3 files changed, 142 deletions(-) diff --git a/tests/mysql/query.rs b/tests/mysql/query.rs index 11470c9e5..98276317d 100644 --- a/tests/mysql/query.rs +++ b/tests/mysql/query.rs @@ -1553,42 +1553,3 @@ fn sub_query_with_fn() { "SELECT ARRAY((SELECT * FROM `character`))" ); } - -#[test] -fn select_like_with_expr() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where(Expr::col(Char::Character).like(Expr::col(Char::FontId))) - .to_string(MysqlQueryBuilder), - r#"SELECT `character` FROM `character` WHERE `character` LIKE `font_id`"# - ); -} - -#[test] -fn select_like_with_function() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where( - Expr::expr(Func::lower(Expr::col(Char::Character))) - .like(Func::lower(Expr::col(Char::FontId))) - ) - .to_string(MysqlQueryBuilder), - r#"SELECT `character` FROM `character` WHERE LOWER(`character`) LIKE LOWER(`font_id`)"# - ); -} - -#[test] -fn select_not_like_with_expr() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where(Expr::col(Char::Character).not_like(Expr::col(Char::FontId))) - .to_string(MysqlQueryBuilder), - r#"SELECT `character` FROM `character` WHERE `character` NOT LIKE `font_id`"# - ); -} diff --git a/tests/postgres/query.rs b/tests/postgres/query.rs index e99470c11..06dd18c48 100644 --- a/tests/postgres/query.rs +++ b/tests/postgres/query.rs @@ -2,7 +2,6 @@ use core::f64; use super::*; use pretty_assertions::assert_eq; -use sea_query::extension::postgres::PgExpr; use sea_query::{audit::AuditTrait, extension::postgres::PgBinOper}; #[test] @@ -2526,66 +2525,3 @@ fn test_pgvector_select() { r#"SELECT "character" FROM "character" WHERE "character" = '[1,2]'"# ); } - -#[test] -fn select_like_with_expr() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where(Expr::col(Char::Character).like(Expr::col(Char::FontId))) - .to_string(PostgresQueryBuilder), - r#"SELECT "character" FROM "character" WHERE "character" LIKE "font_id""# - ); -} - -#[test] -fn select_like_with_function() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where( - Expr::expr(Func::lower(Expr::col(Char::Character))) - .like(Func::lower(Expr::col(Char::FontId))) - ) - .to_string(PostgresQueryBuilder), - r#"SELECT "character" FROM "character" WHERE LOWER("character") LIKE LOWER("font_id")"# - ); -} - -#[test] -fn select_not_like_with_expr() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where(Expr::col(Char::Character).not_like(Expr::col(Char::FontId))) - .to_string(PostgresQueryBuilder), - r#"SELECT "character" FROM "character" WHERE "character" NOT LIKE "font_id""# - ); -} - -#[test] -fn select_ilike_with_expr() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where(Expr::col(Char::Character).ilike(Expr::col(Char::FontId))) - .to_string(PostgresQueryBuilder), - r#"SELECT "character" FROM "character" WHERE "character" ILIKE "font_id""# - ); -} - -#[test] -fn select_not_ilike_with_expr() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where(Expr::col(Char::Character).not_ilike(Expr::col(Char::FontId))) - .to_string(PostgresQueryBuilder), - r#"SELECT "character" FROM "character" WHERE "character" NOT ILIKE "font_id""# - ); -} diff --git a/tests/sqlite/query.rs b/tests/sqlite/query.rs index 47e0150dd..54a5008be 100644 --- a/tests/sqlite/query.rs +++ b/tests/sqlite/query.rs @@ -1859,42 +1859,3 @@ fn recursive_with_multiple_ctes() { r#"WITH RECURSIVE "sub1" ("a") AS (SELECT * FROM "character") , "sub2" ("b") AS (SELECT * FROM "character") SELECT * FROM "sub1" UNION ALL SELECT * FROM "sub2""# ); } - -#[test] -fn select_like_with_expr() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where(Expr::col(Char::Character).like(Expr::col(Char::FontId))) - .to_string(SqliteQueryBuilder), - r#"SELECT "character" FROM "character" WHERE "character" LIKE "font_id""# - ); -} - -#[test] -fn select_like_with_function() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where( - Expr::expr(Func::lower(Expr::col(Char::Character))) - .like(Func::lower(Expr::col(Char::FontId))) - ) - .to_string(SqliteQueryBuilder), - r#"SELECT "character" FROM "character" WHERE LOWER("character") LIKE LOWER("font_id")"# - ); -} - -#[test] -fn select_not_like_with_expr() { - assert_eq!( - Query::select() - .column(Char::Character) - .from(Char::Table) - .and_where(Expr::col(Char::Character).not_like(Expr::col(Char::FontId))) - .to_string(SqliteQueryBuilder), - r#"SELECT "character" FROM "character" WHERE "character" NOT LIKE "font_id""# - ); -} From 4c31e7b0cdccd88472e0bf7c49c67da6bdb2050e Mon Sep 17 00:00:00 2001 From: rahuld109 Date: Fri, 27 Mar 2026 16:23:40 +0530 Subject: [PATCH 6/8] fix: remove private item link in LikeExpr doc comment --- src/types/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/mod.rs b/src/types/mod.rs index f0520f818..cf65188ca 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -171,7 +171,7 @@ pub enum Keyword { /// Like Expression. /// -/// Wraps [`LikeExprInner`] to keep enum variants private, since Rust enum +/// Wraps an internal enum to keep variants private, since Rust enum /// variants are always public. #[derive(Debug, Clone)] pub struct LikeExpr(pub(crate) LikeExprInner); From 157378e4e922a38fb0502075249333e6c8f7c489 Mon Sep 17 00:00:00 2001 From: rahuld109 Date: Fri, 27 Mar 2026 16:33:15 +0530 Subject: [PATCH 7/8] refactor: change like/not_like/ilike/not_ilike to accept Into Replace IntoLikeExpr bound with Into on like(), not_like(), ilike(), and not_ilike(). This allows passing Expr, FunctionCall, and other expression types directly. LikeExpr already implements Into, so existing string-based usage is unchanged. Reverts the LikeExpr enum refactoring as it is no longer needed. --- src/expr/enum.rs | 15 +++-------- src/expr/trait.rs | 12 ++++----- src/extension/postgres/expr.rs | 12 ++++----- src/types/mod.rs | 47 ++++++++++------------------------ 4 files changed, 29 insertions(+), 57 deletions(-) diff --git a/src/expr/enum.rs b/src/expr/enum.rs index 85a24f43b..1f85792a3 100644 --- a/src/expr/enum.rs +++ b/src/expr/enum.rs @@ -868,20 +868,13 @@ impl From for Expr { impl From for Expr { fn from(like: LikeExpr) -> Self { - match like.0 { - crate::LikeExprInner::Str { - pattern, - escape: Some(escape), - } => Self::Binary( - Box::new(pattern.into()), + match like.escape { + Some(escape) => Self::Binary( + Box::new(like.pattern.into()), BinOper::Escape, Box::new(Expr::Constant(escape.into())), ), - crate::LikeExprInner::Str { - pattern, - escape: None, - } => pattern.into(), - crate::LikeExprInner::Expr(expr) => expr, + None => like.pattern.into(), } } } diff --git a/src/expr/trait.rs b/src/expr/trait.rs index 48ebe0a99..818bbb544 100644 --- a/src/expr/trait.rs +++ b/src/expr/trait.rs @@ -920,11 +920,11 @@ pub trait ExprTrait: Sized { /// r#"SELECT "character", "size_w", "size_h" FROM "character" WHERE "character"."character" LIKE '|_Our|_' ESCAPE '|'"# /// ); /// ``` - fn like(self, like: L) -> Expr + fn like(self, right: R) -> Expr where - L: IntoLikeExpr, + R: Into, { - self.binary(BinOper::Like, like.into_like_expr()) + self.binary(BinOper::Like, right) } /// Express a less than (`<`) expression. @@ -1255,11 +1255,11 @@ pub trait ExprTrait: Sized { /// r#"SELECT "character", "size_w", "size_h" FROM "character" WHERE "character"."character" NOT LIKE 'Ours''%'"# /// ); /// ``` - fn not_like(self, like: L) -> Expr + fn not_like(self, right: R) -> Expr where - L: IntoLikeExpr, + R: Into, { - self.binary(BinOper::NotLike, like.into_like_expr()) + self.binary(BinOper::NotLike, right) } /// Express a logical `OR` operation. diff --git a/src/extension/postgres/expr.rs b/src/extension/postgres/expr.rs index 3f57e6961..d956beb36 100644 --- a/src/extension/postgres/expr.rs +++ b/src/extension/postgres/expr.rs @@ -140,19 +140,19 @@ pub trait PgExpr: ExprTrait { /// r#"SELECT "character", "size_w", "size_h" FROM "character" WHERE "character"."character" ILIKE E'Ours\'%'"# /// ); /// ``` - fn ilike(self, like: L) -> Expr + fn ilike(self, right: R) -> Expr where - L: IntoLikeExpr, + R: Into, { - self.binary(PgBinOper::ILike, like.into_like_expr()) + self.binary(PgBinOper::ILike, right) } /// Express a `NOT ILIKE` expression - fn not_ilike(self, like: L) -> Expr + fn not_ilike(self, right: R) -> Expr where - L: IntoLikeExpr, + R: Into, { - self.binary(PgBinOper::NotILike, like.into_like_expr()) + self.binary(PgBinOper::NotILike, right) } /// Express a postgres retrieves JSON field as JSON value (`->`). diff --git a/src/types/mod.rs b/src/types/mod.rs index cf65188ca..3c02d6a1a 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -169,20 +169,11 @@ pub enum Keyword { Custom(DynIden), } -/// Like Expression. -/// -/// Wraps an internal enum to keep variants private, since Rust enum -/// variants are always public. -#[derive(Debug, Clone)] -pub struct LikeExpr(pub(crate) LikeExprInner); - +/// Like Expression #[derive(Debug, Clone)] -pub(crate) enum LikeExprInner { - Str { - pattern: String, - escape: Option, - }, - Expr(crate::Expr), +pub struct LikeExpr { + pub(crate) pattern: String, + pub(crate) escape: Option, } impl LikeExpr { @@ -190,10 +181,10 @@ impl LikeExpr { where T: Into, { - Self(LikeExprInner::Str { + Self { pattern: pattern.into(), escape: None, - }) + } } #[deprecated(since = "0.29.0", note = "Please use the [`LikeExpr::new`] method")] @@ -201,16 +192,16 @@ impl LikeExpr { where T: Into, { - LikeExpr::new(pattern) + Self { + pattern: pattern.into(), + escape: None, + } } pub fn escape(self, c: char) -> Self { - match self.0 { - LikeExprInner::Str { pattern, .. } => Self(LikeExprInner::Str { - pattern, - escape: Some(c), - }), - LikeExprInner::Expr(_) => self, + Self { + pattern: self.pattern, + escape: Some(c), } } } @@ -237,18 +228,6 @@ where } } -impl From for LikeExpr { - fn from(expr: crate::Expr) -> Self { - Self(LikeExprInner::Expr(expr)) - } -} - -impl From for LikeExpr { - fn from(func: crate::FunctionCall) -> Self { - Self(LikeExprInner::Expr(crate::Expr::FunctionCall(func))) - } -} - /// SubQuery operators #[derive(Debug, Copy, Clone, PartialEq)] #[non_exhaustive] From 5f3afeb6e91863900f30bcf123a5ff7d915d706d Mon Sep 17 00:00:00 2001 From: rahuld109 Date: Fri, 27 Mar 2026 18:04:37 +0530 Subject: [PATCH 8/8] fix: remove unused IntoLikeExpr import --- src/extension/postgres/expr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension/postgres/expr.rs b/src/extension/postgres/expr.rs index d956beb36..e2ecf7f31 100644 --- a/src/extension/postgres/expr.rs +++ b/src/extension/postgres/expr.rs @@ -1,5 +1,5 @@ use super::PgBinOper; -use crate::{Expr, ExprTrait, IntoLikeExpr}; +use crate::{Expr, ExprTrait}; /// Postgres-specific operator methods for building expressions. pub trait PgExpr: ExprTrait {