Skip to content

Conversation

@martin-augment
Copy link
Owner

20114: To review by AI

@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

Warning

Rate limit exceeded

@martin-augment has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 43 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr-20114-2026-02-03-07-48-32

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @martin-augment, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request streamlines the implementation and benchmarking of the left and right string manipulation functions. By extracting common logic into a new utility module, it eliminates redundant code and improves maintainability. The consolidation of benchmarks into a single file further simplifies testing and ensures consistent performance evaluation for both functions.

Highlights

  • Unified left and right function implementation: Introduced a new common.rs module with a generic general_left_right function and LeftRightSlicer trait, which left and right functions now utilize, significantly reducing code duplication across different string array types (Utf8, LargeUtf8, Utf8View).
  • Consolidated Benchmarks: The separate benchmark files for left and right functions (left.rs and right.rs) have been removed and replaced by a single, unified benchmark (left_right.rs) that tests both functions, improving testing efficiency and consistency.
  • Code Simplification: The original left.rs and right.rs files are now much smaller and cleaner, delegating their core string slicing logic to the new common module.
Changelog
  • datafusion/functions/Cargo.toml
    • Removed [[bench]] entries for 'left' and 'right'.
    • Added a new [[bench]] entry for 'left_right'.
  • datafusion/functions/benches/left.rs
    • Removed the dedicated benchmark file for the left function.
  • datafusion/functions/benches/left_right.rs
    • New file added, containing unified benchmarks for both left and right functions.
  • datafusion/functions/benches/right.rs
    • Removed the dedicated benchmark file for the right function.
  • datafusion/functions/src/unicode/common.rs
    • New file added, introducing LeftRightSlicer trait, LeftSlicer, RightSlicer structs, left_right_byte_length helper, and general_left_right function for shared string slicing logic.
  • datafusion/functions/src/unicode/left.rs
    • Updated invoke_with_args to call general_left_right::<LeftSlicer>.
    • Removed previously duplicated left implementation logic and helper functions.
    • Updated imports to include the new common module.
  • datafusion/functions/src/unicode/mod.rs
    • Added pub mod common; to expose the new common utilities.
  • datafusion/functions/src/unicode/right.rs
    • Updated invoke_with_args to call general_left_right::<RightSlicer>.
    • Removed previously duplicated right implementation logic and helper functions.
    • Updated imports to include the new common module.
Activity
  • The pull request was created by martin-augment.
  • The author explicitly requested review by AI.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request unifies the implementation of left and right string functions by extracting common logic into a new common.rs module. It also consolidates their respective benchmarks into a single left_right.rs benchmark file. This is a great refactoring that reduces code duplication and improves maintainability. I've suggested a small improvement to the new benchmark file to further reduce duplication.

Comment on lines +73 to +158
// Benchmark with positive n (no optimization needed)
let mut bench_name = if is_string_view {
"string_view_array positive n"
} else {
"string_array positive n"
};
let return_type = if is_string_view {
DataType::Utf8View
} else {
DataType::Utf8
};

let args = create_args(size, 32, false, is_string_view);
group.bench_function(BenchmarkId::new(bench_name, size), |b| {
let arg_fields = args
.iter()
.enumerate()
.map(|(idx, arg)| {
Field::new(format!("arg_{idx}"), arg.data_type(), true).into()
})
.collect::<Vec<_>>();
let config_options = Arc::new(ConfigOptions::default());

b.iter(|| {
black_box(
function
.invoke_with_args(ScalarFunctionArgs {
args: args.clone(),
arg_fields: arg_fields.clone(),
number_rows: size,
return_field: Field::new(
"f",
return_type.clone(),
true,
)
.into(),
config_options: Arc::clone(&config_options),
})
.expect("should work"),
)
})
});

// Benchmark with negative n (triggers optimization)
bench_name = if is_string_view {
"string_view_array negative n"
} else {
"string_array negative n"
};
let return_type = if is_string_view {
DataType::Utf8View
} else {
DataType::Utf8
};

let args = create_args(size, 32, true, is_string_view);
group.bench_function(BenchmarkId::new(bench_name, size), |b| {
let arg_fields = args
.iter()
.enumerate()
.map(|(idx, arg)| {
Field::new(format!("arg_{idx}"), arg.data_type(), true).into()
})
.collect::<Vec<_>>();
let config_options = Arc::new(ConfigOptions::default());

b.iter(|| {
black_box(
function
.invoke_with_args(ScalarFunctionArgs {
args: args.clone(),
arg_fields: arg_fields.clone(),
number_rows: size,
return_field: Field::new(
"f",
return_type.clone(),
true,
)
.into(),
config_options: Arc::clone(&config_options),
})
.expect("should work"),
)
})
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The code for benchmarking with positive and negative n is very similar and can be refactored to reduce duplication. You can introduce a loop over [false, true] for use_negative and move the common benchmarking logic inside.

                let return_type = if is_string_view {
                    DataType::Utf8View
                } else {
                    DataType::Utf8
                };

                for use_negative in [false, true] {
                    let bench_name = format!(
                        "{} {}",
                        if is_string_view {
                            "string_view_array"
                        } else {
                            "string_array"
                        },
                        if use_negative { "negative n" } else { "positive n" }
                    );

                    let args = create_args(size, 32, use_negative, is_string_view);
                    group.bench_function(BenchmarkId::new(bench_name, size), |b| {
                        let arg_fields = args
                            .iter()
                            .enumerate()
                            .map(|(idx, arg)| {
                                Field::new(format!("arg_{idx}"), arg.data_type(), true).into()
                            })
                            .collect::<Vec<_>>();
                        let config_options = Arc::new(ConfigOptions::default());

                        b.iter(|| {
                            black_box(
                                function
                                    .invoke_with_args(ScalarFunctionArgs {
                                        args: args.clone(),
                                        arg_fields: arg_fields.clone(),
                                        number_rows: size,
                                        return_field: Field::new(
                                            "f",
                                            return_type.clone(),
                                            true,
                                        )
                                        .into(),
                                        config_options: Arc::clone(&config_options),
                                    })
                                    .expect("should work"),
                            )
                        })
                    });
                }

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value:good-to-have; category:bug; feedback: The Gemini AI reviewer is correct! There is a code duplication in the benchmark test that could be avoided by adding a new for loop for the negative/positive flag.Prevent maintaining the same code twice.

@claude
Copy link

claude bot commented Feb 3, 2026

Pull Request Review: Unify left and right functions and benches

Summary

This PR refactors the left and right string functions by extracting common logic into a shared module (unicode/common.rs), unifying the benchmark files, and reducing code duplication by ~212 lines. The changes are well-structured and demonstrate good software engineering practices.

✅ Strengths

Code Quality

  • Excellent DRY principle: Successfully eliminates ~212 lines of duplicate code by extracting common logic
  • Clean abstraction: The LeftRightSlicer trait is a simple, elegant solution for handling the differences between left/right operations
  • Proper license headers: All new files include the required Apache 2.0 license header
  • Good documentation: The common module has appropriate module-level documentation
  • Well-structured: Clear separation of concerns with LeftSlicer and RightSlicer implementations

Testing

  • Comprehensive tests: Both left.rs and right.rs retain their full test suites covering:
    • Basic positive/negative values
    • Edge cases (i64::MIN, large values, zero)
    • Null handling
    • Unicode/UTF-8 character handling
    • StringView and String array types
  • Benchmark consolidation: The unified left_right.rs benchmark now tests both functions systematically

Performance Considerations

  • Buffer reuse optimization: Lines 172-173 in common.rs show smart buffer reuse for StringViewArray
  • Efficient slicing: Uses Range<usize> to minimize allocations
  • Inline hints: The #[inline] attribute on left_right_byte_length is appropriate for this hot path function

🔍 Issues & Concerns

1. Bug: Incorrect left() documentation comment (Lines 91-93 in left.rs)

/// left('abcde', 2) = 'ab'
/// left('abcde', -2) = 'abc'  // CORRECT in comment

However, the old code comment said left('abcde', -2) = 'ab' which was incorrect. The new comment correctly states 'abc', which matches the actual behavior and tests. ✅ This is actually a fix!

2. Code duplication in benchmark file (Lines 79-83, 122-126 in left_right.rs)

The return_type variable is calculated twice in identical code blocks:

let return_type = if is_string_view {
    DataType::Utf8View
} else {
    DataType::Utf8
};

This could be extracted before the two benchmark blocks to avoid duplication.

3. Minor: Mutable variable reassignment (Lines 74, 117 in left_right.rs)

let mut bench_name = if is_string_view { ... };
// ... later ...
bench_name = if is_string_view { ... };  // reassignment

The variable doesn't need to be mutable. Consider using separate variables (bench_name_positive, bench_name_negative) or just inline the values in the BenchmarkId::new() call.

4. Integer overflow handling (Line 73 in common.rs)

.nth_back((n.unsigned_abs().min(usize::MAX as u64) - 1) as usize)

This looks correct for preventing overflow when converting i64 to usize, but the subtraction of 1 could theoretically underflow if n.unsigned_abs().min(usize::MAX as u64) equals 0. However, this can't happen because this code is only reached when n < 0 (from the Ordering::Less match arm), so n.unsigned_abs() is always >= 1. Still, a comment explaining this invariant would be helpful.

5. Potential edge case: i64::MIN handling (Line 59-61 in common.rs)

} else if n == i64::MIN {
    // Special case for i64::MIN overflow
    0..0

Good catch on handling this edge case! However, this is only in RightSlicer. The LeftSlicer doesn't have this check. Let me verify if it's needed...

Looking at line 73, when n == i64::MIN, the call n.unsigned_abs().min(usize::MAX as u64) - 1 would work correctly because i64::MIN.unsigned_abs() returns 9223372036854775808u64 (which is i64::MAX + 1). So the LeftSlicer handles it implicitly. ✅ No issue here.

🔒 Security

  • No SQL injection concerns: Functions work on array data, not SQL strings
  • No buffer overflows: Uses safe Rust slicing with proper bounds checking
  • Integer overflow handling: Properly handles edge cases like i64::MIN and large values with .min(usize::MAX as u64)

🚀 Performance

  • No regression expected: The abstraction uses zero-cost traits (monomorphization at compile time)
  • Same algorithms: The core logic remains unchanged, just reorganized
  • Benchmark coverage: The new unified benchmark will help catch any future performance regressions

📋 Suggestions

High Priority

None - the code is solid!

Low Priority (Nice to have)

  1. Add a comment explaining the overflow safety invariant at line 73 in common.rs
  2. Refactor benchmark to eliminate return_type duplication
  3. Remove mut from bench_name or use separate variables

✅ Recommendation

APPROVE - This is a high-quality refactoring PR that improves code maintainability while preserving functionality and test coverage. The minor suggestions above are optional improvements that don't block merging.

The PR successfully achieves its goal of unifying the left and right implementations, and the unified benchmark will make it easier to ensure both functions maintain performance parity going forward.

Great work! 🎉

@augmentcode
Copy link

augmentcode bot commented Feb 3, 2026

🤖 Augment PR Summary

Summary: This PR deduplicates the Unicode string slicing implementations and benchmarks for left and right.

Changes:

  • Introduced datafusion/functions/src/unicode/common.rs with a shared general_left_right implementation and slicer traits for left/right
  • Refactored left and right UDFs to delegate to the shared implementation for Utf8, LargeUtf8, and Utf8View
  • Removed the duplicated per-function Criterion benches (benches/left.rs, benches/right.rs)
  • Added a unified Criterion benchmark (benches/left_right.rs) that runs both functions across array/view inputs and positive/negative n cases
  • Updated Cargo.toml bench configuration to register the new combined bench target

Technical Notes: The new implementation centralizes UTF-8 codepoint-based slicing logic and provides specialized handling for edge cases such as i64::MIN for right.

🤖 Was this summary useful? React with 👍 or 👎

Copy link

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

let range = F::slice(string, n);
let result_bytes = &string.as_bytes()[range.clone()];

let byte_view = ByteView::from(view);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

general_left_right_view calls ByteView::from(view) for every Utf8View element, but other code paths in this repo only use ByteView::from when the view length is > 12 (non-inline). If view is an inline view (short strings), this may decode invalid offset/buffer metadata or even panic; consider avoiding ByteView::from for inline views.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot confirm/reject this claim. The provided information is not enough to decide.

@martin-augment
Copy link
Owner Author

2. Code duplication in benchmark file (Lines 79-83, 122-126 in left_right.rs)

The return_type variable is calculated twice in identical code blocks:

let return_type = if is_string_view {
    DataType::Utf8View
} else {
    DataType::Utf8
};

This could be extracted before the two benchmark blocks to avoid duplication.

3. Minor: Mutable variable reassignment (Lines 74, 117 in left_right.rs)

let mut bench_name = if is_string_view { ... };
// ... later ...
bench_name = if is_string_view { ... };  // reassignment

The variable doesn't need to be mutable. Consider using separate variables (bench_name_positive, bench_name_negative) or just inline the values in the BenchmarkId::new() call.

value:good-to-have; category:bug; feedback: The Claude AI reviewer is correct! There is a code duplication in the benchmark test that could be avoided by adding a new for loop for the negative/positive flag.Prevent maintaining the same code twice.

@martin-augment
Copy link
Owner Author

4. Integer overflow handling (Line 73 in common.rs)

.nth_back((n.unsigned_abs().min(usize::MAX as u64) - 1) as usize)

This looks correct for preventing overflow when converting i64 to usize, but the subtraction of 1 could theoretically underflow if n.unsigned_abs().min(usize::MAX as u64) equals 0. However, this can't happen because this code is only reached when n < 0 (from the Ordering::Less match arm), so n.unsigned_abs() is always >= 1. Still, a comment explaining this invariant would be helpful.

value:annoying; category:bug; feedback: The Claude AI reviewer is not correct! The referred code is within the "Less" branch of n.cmp(&0) check, so it is known that n is not 0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants