@@ -3179,6 +3179,165 @@ struct select_query_builder {
31793179 std::move (on_cond));
31803180 }
31813181
3182+ // natural_join — NATURAL JOIN (implicit column matching, no ON/USING clause)
3183+ template <typename RightTable>
3184+ [[nodiscard]] select_query_builder natural_join () const & {
3185+ auto copy = *this ;
3186+ copy.joins_ += " NATURAL JOIN " + std::string (table_name_for<RightTable>::value ().to_string_view ());
3187+ return copy;
3188+ }
3189+ template <typename RightTable>
3190+ [[nodiscard]] select_query_builder natural_join () && {
3191+ joins_ += " NATURAL JOIN " + std::string (table_name_for<RightTable>::value ().to_string_view ());
3192+ return std::move (*this );
3193+ }
3194+
3195+ // natural_left_join — NATURAL LEFT JOIN
3196+ template <typename RightTable>
3197+ [[nodiscard]] select_query_builder natural_left_join () const & {
3198+ auto copy = *this ;
3199+ copy.joins_ += " NATURAL LEFT JOIN " + std::string (table_name_for<RightTable>::value ().to_string_view ());
3200+ return copy;
3201+ }
3202+ template <typename RightTable>
3203+ [[nodiscard]] select_query_builder natural_left_join () && {
3204+ joins_ += " NATURAL LEFT JOIN " + std::string (table_name_for<RightTable>::value ().to_string_view ());
3205+ return std::move (*this );
3206+ }
3207+
3208+ // natural_right_join — NATURAL RIGHT JOIN
3209+ template <typename RightTable>
3210+ [[nodiscard]] select_query_builder natural_right_join () const & {
3211+ auto copy = *this ;
3212+ copy.joins_ += " NATURAL RIGHT JOIN " + std::string (table_name_for<RightTable>::value ().to_string_view ());
3213+ return copy;
3214+ }
3215+ template <typename RightTable>
3216+ [[nodiscard]] select_query_builder natural_right_join () && {
3217+ joins_ += " NATURAL RIGHT JOIN " + std::string (table_name_for<RightTable>::value ().to_string_view ());
3218+ return std::move (*this );
3219+ }
3220+
3221+ // join_using — JOIN ... USING (col1, col2, ...) variants
3222+ template <typename RightTable, ColumnDescriptor... UsingCols>
3223+ [[nodiscard]] select_query_builder inner_join_using () const & {
3224+ return add_join_using (" INNER JOIN" , table_name_for<RightTable>::value ().to_string_view (),
3225+ build_using_cols_string<UsingCols...>());
3226+ }
3227+ template <typename RightTable, ColumnDescriptor... UsingCols>
3228+ [[nodiscard]] select_query_builder inner_join_using () && {
3229+ return std::move (*this ).add_join_using (" INNER JOIN" , table_name_for<RightTable>::value ().to_string_view (),
3230+ build_using_cols_string<UsingCols...>());
3231+ }
3232+
3233+ template <typename RightTable, ColumnDescriptor... UsingCols>
3234+ [[nodiscard]] select_query_builder left_join_using () const & {
3235+ return add_join_using (" LEFT JOIN" , table_name_for<RightTable>::value ().to_string_view (),
3236+ build_using_cols_string<UsingCols...>());
3237+ }
3238+ template <typename RightTable, ColumnDescriptor... UsingCols>
3239+ [[nodiscard]] select_query_builder left_join_using () && {
3240+ return std::move (*this ).add_join_using (" LEFT JOIN" , table_name_for<RightTable>::value ().to_string_view (),
3241+ build_using_cols_string<UsingCols...>());
3242+ }
3243+
3244+ template <typename RightTable, ColumnDescriptor... UsingCols>
3245+ [[nodiscard]] select_query_builder right_join_using () const & {
3246+ return add_join_using (" RIGHT JOIN" , table_name_for<RightTable>::value ().to_string_view (),
3247+ build_using_cols_string<UsingCols...>());
3248+ }
3249+ template <typename RightTable, ColumnDescriptor... UsingCols>
3250+ [[nodiscard]] select_query_builder right_join_using () && {
3251+ return std::move (*this ).add_join_using (" RIGHT JOIN" , table_name_for<RightTable>::value ().to_string_view (),
3252+ build_using_cols_string<UsingCols...>());
3253+ }
3254+
3255+ template <typename RightTable, ColumnDescriptor... UsingCols>
3256+ [[nodiscard]] select_query_builder full_join_using () const & {
3257+ return add_join_using (" FULL OUTER JOIN" , table_name_for<RightTable>::value ().to_string_view (),
3258+ build_using_cols_string<UsingCols...>());
3259+ }
3260+ template <typename RightTable, ColumnDescriptor... UsingCols>
3261+ [[nodiscard]] select_query_builder full_join_using () && {
3262+ return std::move (*this ).add_join_using (" FULL OUTER JOIN" , table_name_for<RightTable>::value ().to_string_view (),
3263+ build_using_cols_string<UsingCols...>());
3264+ }
3265+
3266+ // lateral_join — JOIN LATERAL (subquery) AS alias (MySQL 8.0+)
3267+ // Pass a pre-built SQL string as the subquery (e.g. inner_query.build_sql()).
3268+ [[nodiscard]] select_query_builder lateral_join (std::string subquery_sql, std::string_view alias) const & {
3269+ auto copy = *this ;
3270+ copy.joins_ += " JOIN LATERAL (" + std::move (subquery_sql) + " ) AS " + std::string (alias);
3271+ return copy;
3272+ }
3273+ [[nodiscard]] select_query_builder lateral_join (std::string subquery_sql, std::string_view alias) && {
3274+ joins_ += " JOIN LATERAL (" + std::move (subquery_sql) + " ) AS " + std::string (alias);
3275+ return std::move (*this );
3276+ }
3277+
3278+ // left_lateral_join — LEFT JOIN LATERAL (subquery) AS alias (MySQL 8.0+)
3279+ [[nodiscard]] select_query_builder left_lateral_join (std::string subquery_sql, std::string_view alias) const & {
3280+ auto copy = *this ;
3281+ copy.joins_ += " LEFT JOIN LATERAL (" + std::move (subquery_sql) + " ) AS " + std::string (alias);
3282+ return copy;
3283+ }
3284+ [[nodiscard]] select_query_builder left_lateral_join (std::string subquery_sql, std::string_view alias) && {
3285+ joins_ += " LEFT JOIN LATERAL (" + std::move (subquery_sql) + " ) AS " + std::string (alias);
3286+ return std::move (*this );
3287+ }
3288+
3289+ // lateral_join_on — JOIN LATERAL (subquery) AS alias ON condition
3290+ [[nodiscard]] select_query_builder lateral_join_on (std::string subquery_sql, std::string_view alias,
3291+ where_condition on_cond) const & {
3292+ auto copy = *this ;
3293+ copy.joins_ += " JOIN LATERAL (" + std::move (subquery_sql) + " ) AS " + std::string (alias) + " ON " +
3294+ on_cond.build_sql ();
3295+ return copy;
3296+ }
3297+ [[nodiscard]] select_query_builder lateral_join_on (std::string subquery_sql, std::string_view alias,
3298+ where_condition on_cond) && {
3299+ joins_ += " JOIN LATERAL (" + std::move (subquery_sql) + " ) AS " + std::string (alias) + " ON " +
3300+ on_cond.build_sql ();
3301+ return std::move (*this );
3302+ }
3303+
3304+ // left_lateral_join_on — LEFT JOIN LATERAL (subquery) AS alias ON condition
3305+ [[nodiscard]] select_query_builder left_lateral_join_on (std::string subquery_sql, std::string_view alias,
3306+ where_condition on_cond) const & {
3307+ auto copy = *this ;
3308+ copy.joins_ += " LEFT JOIN LATERAL (" + std::move (subquery_sql) + " ) AS " + std::string (alias) + " ON " +
3309+ on_cond.build_sql ();
3310+ return copy;
3311+ }
3312+ [[nodiscard]] select_query_builder left_lateral_join_on (std::string subquery_sql, std::string_view alias,
3313+ where_condition on_cond) && {
3314+ joins_ += " LEFT JOIN LATERAL (" + std::move (subquery_sql) + " ) AS " + std::string (alias) + " ON " +
3315+ on_cond.build_sql ();
3316+ return std::move (*this );
3317+ }
3318+
3319+ // straight_join — STRAIGHT_JOIN (MySQL optimizer hint: forces left-to-right join order)
3320+ template <typename RightTable, ColumnDescriptor LeftCol, ColumnDescriptor RightCol>
3321+ [[nodiscard]] select_query_builder straight_join () const & {
3322+ return add_join (" STRAIGHT_JOIN" , table_name_for<RightTable>::value ().to_string_view (),
3323+ qualified_col_name<LeftCol>(), qualified_col_name<RightCol>());
3324+ }
3325+ template <typename RightTable, ColumnDescriptor LeftCol, ColumnDescriptor RightCol>
3326+ [[nodiscard]] select_query_builder straight_join () && {
3327+ return std::move (*this ).add_join (" STRAIGHT_JOIN" , table_name_for<RightTable>::value ().to_string_view (),
3328+ qualified_col_name<LeftCol>(), qualified_col_name<RightCol>());
3329+ }
3330+
3331+ template <typename RightTable>
3332+ [[nodiscard]] select_query_builder straight_join_on (where_condition on_cond) const & {
3333+ return add_join_on (" STRAIGHT_JOIN" , table_name_for<RightTable>::value ().to_string_view (), std::move (on_cond));
3334+ }
3335+ template <typename RightTable>
3336+ [[nodiscard]] select_query_builder straight_join_on (where_condition on_cond) && {
3337+ return std::move (*this ).add_join_on (" STRAIGHT_JOIN" , table_name_for<RightTable>::value ().to_string_view (),
3338+ std::move (on_cond));
3339+ }
3340+
31823341 // ---- Terminal ----
31833342
31843343 [[nodiscard]] std::string build_sql () const {
@@ -3304,6 +3463,28 @@ struct select_query_builder {
33043463 return std::move (*this );
33053464 }
33063465
3466+ [[nodiscard]] select_query_builder add_join_using (std::string_view join_type, std::string_view right_table,
3467+ std::string using_cols) const & {
3468+ auto copy = *this ;
3469+ copy.joins_ +=
3470+ " " + std::string (join_type) + " " + std::string (right_table) + " USING (" + std::move (using_cols) + " )" ;
3471+ return copy;
3472+ }
3473+ [[nodiscard]] select_query_builder add_join_using (std::string_view join_type, std::string_view right_table,
3474+ std::string using_cols) && {
3475+ joins_ +=
3476+ " " + std::string (join_type) + " " + std::string (right_table) + " USING (" + std::move (using_cols) + " )" ;
3477+ return std::move (*this );
3478+ }
3479+
3480+ template <ColumnDescriptor... UsingCols>
3481+ static std::string build_using_cols_string () {
3482+ std::string s;
3483+ bool first = true ;
3484+ ((s += (first ? " " : " , " ), s += std::string (column_traits<UsingCols>::column_name ()), first = false ), ...);
3485+ return s;
3486+ }
3487+
33073488 // Private helper: builds a comma-separated GROUP BY column name string.
33083489 template <ColumnDescriptor... GroupCols>
33093490 static std::string build_group_by_string () {
0 commit comments