feat: expand xBase->SQL push-down coverage (\$ contains, LEFT)#108
Conversation
- xBase '\$' substring test: 'needle' \$ haystack -> haystack LIKE '%needle%', for a literal needle with no LIKE wildcard (field needle or embedded % _ \\ declines, so matching can never change). - LEFT(s, n) -> SUBSTR(s, 1, n) (portable; SQLite has no native LEFT). try_emit_sql_where stays conservative: anything outside the modelled subset still returns nullopt and the caller filters client-side. 11 cases / 116 assertions; full suite 834/834. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request adds support for the xBase $ (substring) operator and the LEFT function to the SQL push-down generator, mapping them to SQL LIKE and SUBSTR respectively, along with corresponding unit tests. However, both features are currently missing implementations in the client-side row interpreter (src/engine/index_expr.cpp). This inconsistency can lead to silent query result corruption or incorrect evaluations if a query falls back to client-side filtering.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if (i < s.size() && s[i] == '$') { | ||
| ++i; | ||
| std::string rhs = emit_value(); | ||
| if (!ok) return ""; | ||
| if (lhs.size() >= 2 && lhs.front() == '\'' && lhs.back() == '\'') { | ||
| std::string inner = lhs.substr(1, lhs.size() - 2); | ||
| if (inner.find('%') == std::string::npos && | ||
| inner.find('_') == std::string::npos && | ||
| inner.find('\\') == std::string::npos) { | ||
| return rhs + " LIKE '%" + inner + "%'"; | ||
| } | ||
| } | ||
| ok = false; | ||
| return ""; | ||
| } |
There was a problem hiding this comment.
While adding the $ (contains) operator to the SQL push-down is a great feature, $ is currently completely unsupported in the client-side row interpreter (eval_cmp in src/engine/index_expr.cpp). Because evaluate_index_expr_truthy does not enforce that the entire expression is consumed, any fallback to client-side filtering (e.g., if combined with an untranslatable predicate like RECNO() > 100) will cause the client-side parser to stop at $, treat the LHS literal as a bare truthy scalar, and silently ignore the rest of the expression (including the RHS and any subsequent .AND./.OR. conditions). For example, 'abc' $ NM .AND. QTY > 0 would evaluate to true for all rows client-side, completely ignoring both the substring check and the quantity check. To prevent silent query result corruption, please implement the $ operator in eval_cmp in src/engine/index_expr.cpp as well.
| if (fn == "LEFT" && args.size() >= 2) | ||
| return d.substr_fn + "(" + args[0] + ", 1, " + args[1] + ")"; |
There was a problem hiding this comment.
While mapping LEFT to SUBSTR in the SQL push-down is correct, LEFT is currently not implemented in the client-side row interpreter (apply_scalar_fn in src/engine/index_expr.cpp). If a query containing LEFT falls back to client-side filtering (for example, if it is combined with an untranslatable function like RECNO()), LEFT will evaluate to an empty string because it falls into the unknown function fallback. This will lead to incorrect query results and inconsistency between server-side and client-side filtering. Please implement LEFT in apply_scalar_fn in src/engine/index_expr.cpp to ensure consistent evaluation.
Follow-up to #107. Widens the conservative
try_emit_sql_wheretranslator:\$(contains):'needle' \$ haystack->haystack LIKE '%needle%', for a literal needle with no LIKE wildcard. A field needle or an embedded%/_/\declines (returns nullopt), so the pushed predicate can never match different rows than the xBase original.LEFT(s, n)->SUBSTR(s, 1, n)(portable; SQLite has no native LEFT).Still conservative — anything outside the modelled subset returns
std::nulloptand the caller filters client-side. Unit tests: 11 cases / 116 assertions; full suite 834/834.