feat: support qualified single-table select columns (rebased from #32) - #76
feat: support qualified single-table select columns (rebased from #32)#76JagritGumber wants to merge 1 commit into
Conversation
| [column] => strip_identifier_quotes(column).to_lowercase(), | ||
| [prefix, column] => { | ||
| let prefix = strip_identifier_quotes(prefix).to_lowercase(); | ||
| if !allowed_prefixes.iter().any(|allowed| *allowed == prefix) { |
There was a problem hiding this comment.
🔴 Case-sensitive alias comparison causes parse errors for non-lowercase table aliases
resolve_single_table_select_column lowercases the prefix from the expression (let prefix = strip_identifier_quotes(prefix).to_lowercase() at crates/sqlcx-core/src/parser/mod.rs:85) but then compares it with exact equality against allowed_prefixes (*allowed == prefix). The aliases in allowed_prefixes come from extract_table_alias, which returns the alias preserving its original case from the SQL string (e.g., postgres.rs:443 returns Some(alias) without lowercasing). This means any query using a non-lowercase alias like SELECT U.id FROM users U will fail: the alias "U" is pushed into allowed_prefixes as-is, but the prefix from the expression is lowercased to "u", so "U" == "u" is false and the query is rejected.
This is inconsistent with the multi-table JOIN path, where AliasMap correctly lowercases on both insert (joins.rs:52) and lookup (joins.rs:56). The same bug affects all three dialect parsers (postgres, mysql, sqlite) since they all pass un-lowered aliases into the shared resolve_single_table_select_column.
| if !allowed_prefixes.iter().any(|allowed| *allowed == prefix) { | |
| if !allowed_prefixes.iter().any(|allowed| allowed.eq_ignore_ascii_case(&prefix)) { |
Was this helpful? React with 👍 or 👎 to provide feedback.
| && !matches!( | ||
| alias.to_lowercase().as_str(), | ||
| "where" | "join" | "order" | "group" | "limit" | "returning" | ||
| ) |
There was a problem hiding this comment.
🟡 extract_table_alias misidentifies SQL keyword SET as a table alias for UPDATE queries
In all three extract_table_alias implementations (postgres, mysql, sqlite), the keyword filter list does not include "set". For UPDATE users SET name = $1 RETURNING ..., the pattern "update users " matches, and the remainder starts with SET. Since "set" isn't in the keyword reject list, it's returned as a table alias. While the practical impact is currently negligible (the spurious "SET" prefix is unlikely to be used as a column qualifier, and is further masked by BUG-0001's case mismatch), it becomes a latent correctness issue once BUG-0001 is fixed — set.column would be accepted as a valid qualified select expression.
The postgres version at postgres.rs:440 filters "where" | "join" | "order" | "group" | "limit" | "returning" but not "set". The mysql (mysql.rs:468) and sqlite (sqlite.rs:379) versions also omit it (and additionally omit "returning").
| && !matches!( | |
| alias.to_lowercase().as_str(), | |
| "where" | "join" | "order" | "group" | "limit" | "returning" | |
| ) | |
| if !alias.is_empty() | |
| && !matches!( | |
| alias.to_lowercase().as_str(), | |
| "where" | "set" | "join" | "inner" | "left" | "right" | "full" | "cross" | "natural" | "on" | "order" | "group" | "limit" | "having" | "returning" | "values" | "using" | "union" | "intersect" | "except" | |
| ) |
Was this helpful? React with 👍 or 👎 to provide feedback.
Rebase + merge of #32 onto current main. Original PR was written before the INNER JOIN multi-table resolver landed, so the rebase had to combine:
has_outer_join(sql)→resolve_multi_table_columns)resolve_single_table_select_columnwithallowed_prefixes)Both now coexist in each dialect's
resolve_return_columns. Single-table qualified projections likeSELECT users.id, users.name AS user_name FROM usersresolve against the inferred base table; multi-table qualified projections still go through the JOIN resolver.Also dropped
ensure_supported_select_exprandmake_unknown_columnfromparser/mod.rs(no callers remain after the swap), and updated the stalejoins.rsmodule docstring that described pre-INNER-JOIN state.Closes #32.
Test plan
parses_qualified_single_table_selectparser tests + one CLI test, replaces thesingle_table_path_still_rejects_qualified_selectspostgres test with the newsingle_table_path_accepts_qualified_selects)