@@ -57,6 +57,13 @@ enum class sql_cast_type {
5757
5858enum class sort_order { asc, desc };
5959
60+ enum class match_search_modifier {
61+ natural_language, // MATCH(...) AGAINST ('expr') — default, no modifier emitted
62+ boolean_mode, // MATCH(...) AGAINST ('expr' IN BOOLEAN MODE)
63+ query_expansion, // MATCH(...) AGAINST ('expr' WITH QUERY EXPANSION)
64+ natural_language_with_query_expansion, // MATCH(...) AGAINST ('expr' IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION)
65+ };
66+
6067template <typename ... Cols>
6168struct grouping_set {};
6269
@@ -292,6 +299,13 @@ struct where_condition {
292299// is_null<Col>() — col IS NULL
293300// is_not_null<Col>() — col IS NOT NULL
294301// like<Col>(pattern) — col LIKE 'pattern'
302+ // not_like<Col>(pattern) — col NOT LIKE 'pattern'
303+ // regexp<Col>(pattern) — col REGEXP 'pattern'
304+ // not_regexp<Col>(pattern) — col NOT REGEXP 'pattern'
305+ // rlike<Col>(pattern) — col RLIKE 'pattern' (MySQL synonym for REGEXP)
306+ // not_rlike<Col>(pattern) — col NOT RLIKE 'pattern'
307+ // sounds_like<Col>(word) — col SOUNDS LIKE 'word'
308+ // match_against<C1,C2,...>(expr) — MATCH(c1,c2,...) AGAINST ('expr')
295309// and_(a, b) — (a AND b)
296310// or_(a, b) — (a OR b)
297311// not_(cond) — NOT (cond)
@@ -513,6 +527,83 @@ template <ColumnFieldType Col>
513527 return {column_traits<Col>::column_name (), " REGEXP " , std::move (rhs)};
514528}
515529
530+ // not_regexp<Col>(pattern) — col NOT REGEXP 'pattern'
531+ template <ColumnFieldType Col>
532+ [[nodiscard]] where_condition not_regexp (std::string_view pattern) {
533+ auto escaped = sql_detail::escape_sql_string (pattern);
534+ std::string rhs;
535+ rhs.reserve (2 + escaped.size ());
536+ rhs += ' \' ' ;
537+ rhs += std::move (escaped);
538+ rhs += ' \' ' ;
539+ return {column_traits<Col>::column_name (), " NOT REGEXP " , std::move (rhs)};
540+ }
541+
542+ // rlike<Col>(pattern) — col RLIKE 'pattern' (MySQL synonym for REGEXP)
543+ template <ColumnFieldType Col>
544+ [[nodiscard]] where_condition rlike (std::string_view pattern) {
545+ return regexp<Col>(pattern);
546+ }
547+
548+ // not_rlike<Col>(pattern) — col NOT RLIKE 'pattern'
549+ template <ColumnFieldType Col>
550+ [[nodiscard]] where_condition not_rlike (std::string_view pattern) {
551+ return not_regexp<Col>(pattern);
552+ }
553+
554+ // sounds_like<Col>(word) — col SOUNDS LIKE 'word'
555+ template <ColumnFieldType Col>
556+ [[nodiscard]] where_condition sounds_like (std::string_view word) {
557+ auto escaped = sql_detail::escape_sql_string (word);
558+ std::string rhs;
559+ rhs.reserve (2 + escaped.size ());
560+ rhs += ' \' ' ;
561+ rhs += std::move (escaped);
562+ rhs += ' \' ' ;
563+ return {column_traits<Col>::column_name (), " SOUNDS LIKE " , std::move (rhs)};
564+ }
565+
566+ // match_against<Cols...>(expr [, modifier]) — MATCH(col1, col2, ...) AGAINST ('expr' [modifier])
567+ //
568+ // modifier is one of:
569+ // match_search_modifier::natural_language (default) — no modifier emitted
570+ // match_search_modifier::boolean_mode — IN BOOLEAN MODE
571+ // match_search_modifier::query_expansion — WITH QUERY EXPANSION
572+ // match_search_modifier::natural_language_with_query_expansion — IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION
573+ template <ColumnFieldType... Cols>
574+ requires (sizeof ...(Cols) > 0 )
575+ [[nodiscard]] where_condition match_against (std::string_view expr,
576+ match_search_modifier modifier = match_search_modifier::natural_language) {
577+ std::string s;
578+ s.reserve (64 + expr.size ());
579+ s += " MATCH(" ;
580+ bool first = true ;
581+ auto add_col = [&](std::string_view name) {
582+ if (!first) s += " , " ;
583+ s += name;
584+ first = false ;
585+ };
586+ (add_col (column_traits<Cols>::column_name ()), ...);
587+ s += " ) AGAINST ('" ;
588+ s += sql_detail::escape_sql_string (expr);
589+ s += ' \' ' ;
590+ switch (modifier) {
591+ case match_search_modifier::boolean_mode:
592+ s += " IN BOOLEAN MODE" ;
593+ break ;
594+ case match_search_modifier::query_expansion:
595+ s += " WITH QUERY EXPANSION" ;
596+ break ;
597+ case match_search_modifier::natural_language_with_query_expansion:
598+ s += " IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION" ;
599+ break ;
600+ case match_search_modifier::natural_language:
601+ break ;
602+ }
603+ s += ' )' ;
604+ return {{}, {}, std::move (s)};
605+ }
606+
516607template <ColumnFieldType Col, AnySelectQuery Query>
517608[[nodiscard]] where_condition in_subquery (Query const & subquery) {
518609 auto sub = subquery.build_sql ();
@@ -575,9 +666,14 @@ template <AnySelectQuery Query>
575666// col_ref<trade::name>.is_not_null() → is_not_null<trade::name>()
576667// col_ref<trade::code>.like("AAPL%") → like<trade::code>("AAPL%")
577668// col_ref<trade::code>.not_like("X%") → not_like<trade::code>("X%")
578- // col_ref<trade::id>.between(1u, 10u) → between<trade::id>(1u, 10u)
579- // col_ref<trade::code>.in({"AAPL", "GOOGL"}) → in<trade::code>({"AAPL", "GOOGL"})
580- // col_ref<trade::code>.not_in({"X", "Y"}) → not_in<trade::code>({"X", "Y"})
669+ // col_ref<trade::id>.between(1u, 10u) → between<trade::id>(1u, 10u)
670+ // col_ref<trade::code>.in({"AAPL", "GOOGL"}) → in<trade::code>({"AAPL", "GOOGL"})
671+ // col_ref<trade::code>.not_in({"X", "Y"}) → not_in<trade::code>({"X", "Y"})
672+ // col_ref<trade::code>.regexp("^A") → regexp<trade::code>("^A")
673+ // col_ref<trade::code>.not_regexp("^A") → not_regexp<trade::code>("^A")
674+ // col_ref<trade::code>.rlike("^A") → rlike<trade::code>("^A")
675+ // col_ref<trade::code>.not_rlike("^A") → not_rlike<trade::code>("^A")
676+ // col_ref<trade::name>.sounds_like("word") → sounds_like<trade::name>("word")
581677//
582678// Composing with &, |, !:
583679// col_ref<trade::code> == "AAPL" | col_ref<trade::code> == "GOOGL"
@@ -646,6 +742,18 @@ struct col_expr {
646742 [[nodiscard]] where_condition regexp (std::string_view pattern) const {
647743 return ds_mysql::regexp<Col>(pattern);
648744 }
745+ [[nodiscard]] where_condition not_regexp (std::string_view pattern) const {
746+ return ds_mysql::not_regexp<Col>(pattern);
747+ }
748+ [[nodiscard]] where_condition rlike (std::string_view pattern) const {
749+ return ds_mysql::rlike<Col>(pattern);
750+ }
751+ [[nodiscard]] where_condition not_rlike (std::string_view pattern) const {
752+ return ds_mysql::not_rlike<Col>(pattern);
753+ }
754+ [[nodiscard]] where_condition sounds_like (std::string_view word) const {
755+ return ds_mysql::sounds_like<Col>(word);
756+ }
649757
650758 [[nodiscard]] where_condition null_safe_equal (value_type const & val) const {
651759 return ds_mysql::null_safe_equal<Col>(Col{val});
0 commit comments