Repository navigation
Offer the join operators SQLite offers - #1569
Merged
Merged
Conversation
SQLite combines its join operators freely with an explicit constraint, an implicit one, and NATURAL: - JOIN, INNER JOIN, LEFT [OUTER] JOIN, and as of SQLite 3.39 RIGHT [OUTER] JOIN and FULL [OUTER] JOIN serialize with their constraint left out. - NATURAL combines with INNER, LEFT [OUTER], and as of SQLite 3.39 RIGHT [OUTER] and FULL [OUTER] JOIN. - Executed, they yield the rows SQLite defines for them. - Every one is a join node, with an implicit constraint where none is spelled out. These tests fail until sqlite_orm offers these join operators. Co-Authored-By: Claude Opus 5.5
The six join nodes were six near-identical structs, told apart by their
keyword alone since every join carries a constraint. They are now aliases
of one node, join_clause<Keyword, T, Constraint>, like the binary operators
are of binary_operator:
template<class T, class O>
using left_join_t = join_clause<left_join_string, T, O>;
template<class T>
using cross_join_t = join_clause<cross_join_string, T, implicit_join_constraint>;
is_any_join_v recognizes the node instead of listing the six. The node
names, the factories and the serialization stay as they are.
Co-Authored-By: Claude Opus 5.5
SQLite combines its join operators freely with ON, USING or no constraint, and with NATURAL. Each combination is a keyword and an alias of the join node, with its factory: - join<T>(), inner_join<T>(), left_join<T>() and left_outer_join<T>() without a constraint, next to their constrained overloads. - natural_inner_join<T>(), natural_left_join<T>() and natural_left_outer_join<T>(), which take no constraint by construction. - As of SQLite 3.39: right_join, right_outer_join, full_join and full_outer_join, with or without a constraint, and natural_right_join, natural_right_outer_join, natural_full_join and natural_full_outer_join. Every factory has a C++20 overload taking an alias, like the existing ones. TODO.md loses RIGHT/FULL JOIN and the combinations. Co-Authored-By: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5
fnc12
approved these changes
Oct 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1568: based on its branch, so this diff shows only the changes on top of it.
Summary
SQLite's grammar combines join operators and constraints freely:
[NATURAL] [LEFT [OUTER] | RIGHT [OUTER] | FULL [OUTER] | INNER | CROSS] JOIN, followed byON expr,USING (columns), or nothing (and nothing for NATURAL). sqlite_orm offered six fixed combinations. #1568 made every join carry a constraint, explicit or implicit, and this PR builds the rest on that.One generic join node
The six join structs become aliases of one node,
join_clause<Keyword, T, Constraint>, as the binary operators are aliases ofbinary_operatorsince #1552:is_any_join_vnow recognizes the node instead of listing six templates. The node names, the factories and the serialization stay as they are. The static tests that nameinternal::left_join_t<…>keep working through the aliases.The new join operators
join<T>(),inner_join<T>(),left_join<T>(),left_outer_join<T>()natural_inner_join<T>(),natural_left_join<T>(),natural_left_outer_join<T>()NATURAL INNER JOIN, …right_join,right_outer_join,full_join,full_outer_joinRIGHT [OUTER] JOIN,FULL [OUTER] JOINnatural_right_join,natural_right_outer_join,natural_full_join,natural_full_outer_joinNATURAL RIGHT [OUTER] JOIN, …natural_joinandleft_outer_join. Since they take no constraint, NATURAL can't get an ON or USING.SQLITE_VERSION_NUMBER >= 3039000.TODO.mddrops RIGHT/FULL JOIN and the combination entry from Treat every join as constrained, explicitly or implicitly #1568.CROSS JOIN … ON(which SQLite also accepts) is left out, as it's an inner join in all but name.Test plan
The first commit adds the tests on their own; the generic node and the new operators follow.
tests/statement_serializer_tests/ast/join_operators.cpp: every new operator serializes, with the constraint left out or spelled out, including an aliased NATURAL LEFT JOIN.tests/join_tests.cpp: executed against two tables that share the columnid, the joins yield the rows SQLite defines. That's JOIN and LEFT JOIN without a constraint, NATURAL INNER/LEFT/LEFT OUTER, and on SQLite 3.39+ RIGHT JOIN, NATURAL RIGHT JOIN, FULL OUTER JOIN USING and NATURAL FULL JOIN.tests/static_tests/node_tuple.cpp: every new operator is a join node, with an implicit constraint where none is spelled out, andleft_join<User>()isleft_join_t<User, implicit_join_constraint>.🤖 Generated with Claude Code