Conversation
The CHECK constraint evaluator was failing for compound expressions like `CHECK (age >= 0 AND age <= 150)` because it only looked for the first comparison operator and tried to parse everything after it as a number. This fix adds proper handling for: - AND expressions: recursively evaluate both sides with && - OR expressions: recursively evaluate both sides with || - Parentheses: strip outer parens and recurse - Numeric operands: extract only the numeric portion before operators The implementation respects parenthesis nesting to correctly split on top-level logical operators. Fixes #24 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Code Review - PR #38: Fix CHECK constraint compound expressionsSummaryThis PR successfully fixes issue #24 by properly handling compound CHECK constraint expressions with AND/OR operators. The implementation is functional and the tests pass. However, there are several critical allocation issues that violate TurDB's zero-allocation principle. Critical Issues - Zero Allocation Violations1. String Allocation in extract_numeric_operand (Line 4501-4504)Problem: Allocates a new String on every CHECK constraint evaluation via chars().collect(). Fix: Use byte slicing to find the numeric prefix without allocation. 2. String Allocations in eval_check_expr_recursive (Line 4394)Problem: to_lowercase() allocates on every recursive call. For 'age >= 0 AND age <= 150', this allocates 3 times. Fix: Use eq_ignore_ascii_case() for case-insensitive comparison without allocation. 3. Additional Allocations in eval_simple_comparison (Line 4471-4472)Problem: Two more to_lowercase() calls per simple comparison. Fix: Use case-insensitive byte comparison with windows() and eq_ignore_ascii_case(). Performance ImpactCurrent: For CHECK (age >= 0 AND age <= 150), allocates ~7-9 times per INSERT. For bulk inserts of 10,000 rows: 70,000-90,000 unnecessary heap allocations. Other Issues4. Logic Bug: Operator Precedence IncorrectLines 4396-4404 evaluate AND before OR with equal precedence. SQL standard requires proper precedence (AND binds tighter) or left-to-right with parentheses. 5. Missing Edge Cases in TestsMissing tests for: nested parentheses, multiple AND/OR chains, mixed operators, no-space variations. 6. Inconsistent Error HandlingLine 4496 returns true when parsing fails, silently allowing malformed constraints. Positive Aspects
RecommendationsPriority 1 (Must Fix)
Priority 2 (Should Fix)
ConclusionThis PR successfully solves the immediate problem, but violates TurDB core zero-allocation principle. The implementation allocates multiple strings per CHECK evaluation, which is unacceptable for a database targeting embedded/IoT devices. Recommendation: Request changes to eliminate allocations before merging. Great work identifying the root cause! The recursive approach is sound, just needs zero-copy refactoring. |
Address critical issues from code review: 1. Zero-allocation violations fixed: - Replace to_lowercase() with byte-level case-insensitive comparison - Replace chars().collect() with byte slicing for numeric extraction - Add contains_ignore_ascii_case() using eq_ignore_ascii_case on windows 2. Operator precedence fixed: - Check OR first (lower precedence), then AND (higher precedence) - "a OR b AND c" now correctly evaluates as "a OR (b AND c)" 3. Performance impact eliminated: - Previously: 7-9 allocations per CHECK evaluation - Now: 0 allocations (all byte-level operations) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Addressed Code Review FeedbackFixed in d93bd77: 1. Zero-allocation violations
2. Operator precedence
3. Performance impact
|
Code Review: CHECK Constraint Compound Expression FixThis PR fixes the compound CHECK constraint bug. The recursive evaluation is sound, but there are critical issues that need addressing: Critical Issues1. Zero-Allocation Violation (database.rs:4538-4546) enum CompareOp { Ge, Le, Gt, Lt }
impl CompareOp {
fn compare(self, lhs: f64, rhs: f64) -> bool {
match self { Ge => lhs >= rhs, Le => lhs <= rhs, ... }
}
}2. Case Sensitivity (database.rs:4396, 4401) 3. Fail-Open Semantics (database.rs:4472-4496) 4. No Recursion Depth Limit Testing GapsMissing tests for: OR operator, mixed-case operators, negative numbers, invalid expressions. RecommendationPlease address issues 1-3 before merging (zero-allocation, SQL compliance, data integrity). Review per .claude/rules/ guidelines |
1. Replace closure with CheckCompareOp enum - Closure |v, t| v >= t replaced with enum-based comparison - Enum uses match with #[derive(Clone, Copy)] for zero overhead 2. Fix case sensitivity for AND/OR - Use eq_ignore_ascii_case for keyword matching - Handles all case variants: 'Or', 'AnD', 'AND', etc. 3. Fail-closed semantics - Return false when expression cannot be evaluated - Data integrity: malformed constraints now reject inserts 4. Add recursion depth limit - MAX_CHECK_EXPR_DEPTH = 32 - Prevents stack overflow from deeply nested expressions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Addressed Second Code Review RoundFixed in ab213cd: 1. Zero-allocation: Closure → Enum
2. Case sensitivity
3. Fail-closed semantics
4. Recursion depth limit
|
Code Review - PR #38Great work fixing the compound CHECK constraint bug! The recursive evaluation approach is sound. I have identified a few issues that should be addressed. Critical Issues (Must Fix Before Merge)1. Unbalanced Parenthesis Handling (database.rs:4461)The code does not handle malformed expressions with more closing parens than opening parens. When depth goes negative, it continues processing which could cause incorrect splits. Current: depth -= 1 can go negative 2. Integer Precision Loss for Large Values (database.rs:4571)Converting i64 to f64 loses precision for values beyond 2^53. For example, CHECK (id >= 9007199254740993) will not work correctly. Current: OwnedValue::Int(v) => op.compare(*v as f64, threshold) Performance Optimizations (Should Fix)3. Redundant Parameter (database.rs:4448-4452)The bytes parameter is redundant since it is always original.as_bytes(). Remove it and compute inside the function. 4. Double Trim (database.rs:4476)strip_outer_parens calls trim() on input that is already trimmed at line 4427. Remove the redundant trim. 5. Redundant Bounds Check (database.rs:4462)The while condition already ensures sufficient space. Remove bytes[i..].len() >= op.len() check. Good Findings
Test Coverage GapsConsider adding tests for:
SummaryOverall: Strong fix for the compound CHECK constraint bug. The recursive approach is clean and maintainable. Must fix before merge: Issues 1 and 2 |
- Fix unbalanced parenthesis handling: return None when depth goes negative - Fix integer precision loss: compare as i64 when threshold is a whole number - Remove redundant bytes parameter from split_on_logical_op_case_insensitive - Remove double trim in strip_outer_parens (caller already trims) - Remove redundant bounds check in split function Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Pull Request Review: Fix CHECK Constraint Compound ExpressionsOverviewThis PR fixes a critical bug in CHECK constraint evaluation where compound expressions with AND/OR operators were not properly evaluated. Positive Aspects
Critical Issues1. Inconsistent Error Handling (BLOCKING)Lines 4418-4420: When max depth exceeded, returns false (rejects valid data). Should return Result with error instead. 2. Missing Edge Case Tests (BLOCKING)Per .claude/rules/TESTING.md, need tests for:
3. Unchecked Byte Index (Potential Panic)Lines 4456-4468: Need early return if op is empty to prevent panic on byte slicing. Performance Issues4. Repeated String Scanning (HIGH PRIORITY)Lines 4498-4513: Scans string 4 times sequentially for >=, <=, >, <. Should use single-pass parsing. 5. Weak Numeric ValidationLines 4543-4560: Allows invalid strings like "...", "+-". Need proper state machine validation. Code Quality6. Missing Documentation (STYLE.md violation)CheckCompareOp enum and helper functions need doc comments per .claude/rules/STYLE.md. 7. Integer Overflow RiskLine 4450: Use usize instead of i32 for depth with saturating_sub. Code Quality Score: 7/10
Recommended ActionsMust Fix (Blocking)
Should Fix (High Priority)
Nice to Have
Overall Verdict: Approve with required changes The PR correctly solves issue #24 and maintains zero-allocation compliance. Needs additional edge case tests, proper error handling for max depth, and single-pass operator detection. |
- Return Result<bool> instead of bool when max depth exceeded - Use usize with saturating arithmetic for depth counter - Implement single-pass operator scanning instead of 4 sequential scans - Strengthen numeric validation with state machine - Add doc comments to CheckCompareOp enum and helpers - Support UnaryOp expressions (negative numbers) in expr_to_string - Add comprehensive edge case tests: - Negative numbers: CHECK (temp >= -273.15) - Float comparisons with range - Deeply nested parentheses - Mixed case operators (AnD) - Max depth limit with nested AND - Multiple OR conditions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Code Review - PR #38: Fix CHECK Constraint Compound ExpressionsSummaryThis PR successfully fixes the bug where compound CHECK expressions with AND/OR operators were not being evaluated correctly. The implementation is well-designed with proper recursion handling and zero allocations in the hot path. ✅ 🎯 Strengths1. Zero-Allocation Implementation ⭐The implementation achieves true zero-allocation in the critical path:
2. Proper Error Handling
3. Robust Edge Case Handling
4. Good Test CoverageNew tests in
|
🐛 Issues FoundCRITICAL: Existing Test Failures (Likely Pre-existing)The test suite shows 6 failures related to B-tree operations (NOT related to this PR): These appear to be pre-existing issues in the B-tree implementation, not introduced by this PR. File Length Approaching Limit
🔍 Code Quality Analysisdatabase.rs:4463-4490 -
|
🎨 Style Compliance✅ Follows ERRORS.md
✅ Follows MEMORY.md
|
📊 Allocation Analysis (Zero-Alloc Target ✅)Hot Path (per INSERT/UPDATE):
Overall: ZERO-ALLOCATION TARGET ACHIEVED 🎉Note: 🔧 Minor Improvements (Optional)1. Add AND Short-Circuit (database.rs:4449-4452)if let Some((left, right)) = Self::split_on_logical_op_case_insensitive(trimmed, b" and ") {
let left_result = Self::eval_check_expr_with_depth(left, col_name, value, depth + 1)?;
if !left_result {
return Ok(false); // No need to evaluate right
}
let right_result = Self::eval_check_expr_with_depth(right, col_name, value, depth + 1)?;
return Ok(right_result); // left is true, return right
}2. Add Inline DocumentationAdd doc comments to helper functions explaining invariants: /// Splits expression on logical operator at depth 0 (respecting parentheses).
///
/// Returns None if operator not found at depth 0, or if split would create empty parts.
fn split_on_logical_op_case_insensitive<'a>(/* ... */)3. Consider Extracting to Module (database.rs:4401-4633)Create |
✅ Final VerdictAPPROVE with minor suggestions for future work. What's Great:✅ Fixes the reported bug completely Follow-up Items (not blockers):
Pre-existing Issues (not related to this PR):
Test Coverage Suggestions (follow-up):
Great work on this fix! The implementation demonstrates excellent understanding of TurDB's zero-allocation principles and proper Rust idioms. 🚀 |
Summary
CHECK (age >= 0 AND age <= 150)would not reject invalid values like-5Root Cause
The naive string parsing found the first comparison operator (e.g.,
>=) and tried to parse everything after it as a number. Forage >= 0 AND age <= 150, this meant parsing"0 AND age <= 150"as a number, which failed and defaulted to returningtrue.Solution
Test plan
check_constraint_violation_returns_errornow passes (was failing)Fixes #24
🤖 Generated with Claude Code