-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat(orm): Add JOIN support to Mapper/BaseBuilder and FK auto-detection in code generator #2491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b7ef924
2d88a91
d005037
b2c6157
e95e5f3
4fcd7ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,69 @@ struct Filter | |
| std::string value; | ||
| }; | ||
|
|
||
| /** | ||
| * @brief Represents a SQL JOIN clause. | ||
| */ | ||
| enum class JoinType | ||
| { | ||
| InnerJoin, | ||
| LeftJoin, | ||
| RightJoin, | ||
| FullJoin | ||
| }; | ||
|
|
||
| inline std::string to_join_string(JoinType type) | ||
| { | ||
| switch (type) | ||
| { | ||
| case JoinType::InnerJoin: | ||
| return "INNER JOIN"; | ||
| case JoinType::LeftJoin: | ||
| return "LEFT JOIN"; | ||
| case JoinType::RightJoin: | ||
| return "RIGHT JOIN"; | ||
| case JoinType::FullJoin: | ||
| return "FULL JOIN"; | ||
| } | ||
| // Should never reach here | ||
| return "INNER JOIN"; | ||
| } | ||
|
Comment on lines
+81
to
+96
|
||
|
|
||
| struct JoinClause | ||
| { | ||
| JoinType type; | ||
| std::string table; | ||
| std::string onLeft; // e.g. "users.id" | ||
| std::string onRight; // e.g. "posts.user_id" | ||
| }; | ||
|
|
||
| /** | ||
| * @brief Validate that a string is a safe SQL identifier. | ||
| * | ||
| * Only allows alphanumeric characters, underscores, and dots | ||
| * (for table.column notation). This prevents SQL injection when | ||
| * building JOIN clauses from user-provided identifiers. | ||
| * | ||
| * @param identifier The identifier to validate. | ||
| * @return true if the identifier is safe to use in SQL. | ||
| */ | ||
| inline bool isValidSqlIdentifier(const std::string &identifier) | ||
| { | ||
| if (identifier.empty()) | ||
| { | ||
| return false; | ||
| } | ||
| for (auto c : identifier) | ||
| { | ||
| if (!std::isalnum(static_cast<unsigned char>(c)) && c != '_' && | ||
| c != '.') | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| // Forward declaration to be a friend | ||
| template <typename T, bool SelectAll, bool Single = false> | ||
| class TransformBuilder; | ||
|
|
@@ -87,6 +150,7 @@ class BaseBuilder | |
| std::string from_; | ||
| std::string columns_; | ||
| std::vector<Filter> filters_; | ||
| std::vector<JoinClause> joins_; | ||
| std::optional<std::uint64_t> limit_; | ||
| std::optional<std::uint64_t> offset_; | ||
| // The order is important; use vector<pair> instead of unordered_map and | ||
|
|
@@ -122,6 +186,11 @@ class BaseBuilder | |
| }; | ||
|
|
||
| std::string sql = "select " + columns_ + " from " + from_; | ||
| for (const auto &join : joins_) | ||
| { | ||
| sql += " " + to_join_string(join.type) + " " + join.table + " ON " + | ||
| join.onLeft + " = " + join.onRight; | ||
| } | ||
|
Comment on lines
188
to
+193
|
||
| if (!filters_.empty()) | ||
| { | ||
| sql += " where " + filters_[0].column + " " + | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PostgreSQL FK introspection join is incomplete:
rc.unique_constraint_name = ccu.constraint_nameshould also be joined on the constraint schema (e.g.,rc.unique_constraint_schema = ccu.constraint_schema). Without it, the query can mis-resolve referenced tables/columns when constraint names collide across schemas or return incorrect/multiple matches.