Skip to content
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

Implement substr function #341

Merged
merged 6 commits into from
Sep 15, 2021
Merged

Conversation

vbbono
Copy link
Contributor

@vbbono vbbono commented Sep 7, 2021

Resolve #293

substr ( string text, start integer [, count integer ] ) → text

Extracts the substring of string starting at the start'th character, and extending for count characters if that is specified. (Same as substring(string from start for count).

@panarch panarch self-requested a review September 7, 2021 12:50
@panarch panarch added the enhancement New feature or request label Sep 7, 2021
@codecov-commenter
Copy link

codecov-commenter commented Sep 7, 2021

Codecov Report

Merging #341 (3a2aef8) into main (ec269ad) will increase coverage by 0.05%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #341      +/-   ##
==========================================
+ Coverage   91.20%   91.25%   +0.05%     
==========================================
  Files         126      127       +1     
  Lines        7933     8005      +72     
==========================================
+ Hits         7235     7305      +70     
- Misses        698      700       +2     
Impacted Files Coverage Δ
src/tests/mod.rs 100.00% <ø> (ø)
src/executor/evaluate/mod.rs 94.04% <100.00%> (+0.29%) ⬆️
src/tests/function/substr.rs 100.00% <100.00%> (ø)
src/translate/function.rs 100.00% <100.00%> (ø)
src/executor/evaluate/evaluated.rs 86.31% <0.00%> (-2.11%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ec269ad...3a2aef8. Read the comment docs.

Copy link
Member

@panarch panarch left a comment

Choose a reason for hiding this comment

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

Looks really good!
Let's make it a bit more Rust's way :)
Then, current codes will become even more simple to read.

Comment on lines 262 to 266
let count = if args.len() == 2 {
None
} else {
Some(translate_expr(args[2])?)
};
Copy link
Member

Choose a reason for hiding this comment

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

fyi. Let's get familiar with using methods provided by bool and Option :)
https://doc.rust-lang.org/std/primitive.bool.html#method.then
https://doc.rust-lang.org/std/option/enum.Option.html#method.transpose

let count = (args.len() > 2)         
    .then(|| translate_expr(args[2]))
    .transpose()?;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the information.
Edited as you said.

Comment on lines 617 to 626
let count = match count {
Some(expr) => match eval_to_integer(expr).await? {
Nullable::Value(v) => match v {
x if x < 0 => return Err(EvaluateError::NegativeSubstrLenNotAllowed.into()),
_ => v,
},
Nullable::Null => -1,
},
None => -1,
};
Copy link
Member

Choose a reason for hiding this comment

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

Current count variable has two independent roles

  1. whether count exists or not
  2. number of chars to substr.

ast already properly provides as a form of Option<Expr> and you'll be able to convert it into Option<usize>.
Then, you can handle Option<usize> directly without merging two info into i64 type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know if it's okay to change type of count to Option<usize>,, 😹
Because, to calculate the variable e, count may have to be added to the start, which causes type casting.
Is there any simpler way? XD

@panarch panarch self-requested a review September 14, 2021 03:54
Copy link
Member

@panarch panarch left a comment

Choose a reason for hiding this comment

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

Now it's really close to the merge, thanks!

Comment on lines 120 to 125
r#"SELECT SUBSTR("ABC", -1, NULL) AS test FROM SingleItem"#,
Ok(select!(
"test"
Str;
"ABC".to_owned()
)),
Copy link
Member

Choose a reason for hiding this comment

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

It is supposed to be NULL if any param value is NULL.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I'll change TC to return NULL!

Comment on lines 612 to 646
let start = match eval_to_integer(start).await? {
Nullable::Value(v) => v,
Nullable::Null => return Ok(Evaluated::from(Value::Null)),
};

let count = match count {
Some(expr) => match eval_to_integer(expr).await? {
Nullable::Value(v) => match v {
x if x < 0 => return Err(EvaluateError::NegativeSubstrLenNotAllowed.into()),
_ => Some(v),
},
Nullable::Null => None,
},
None => None,
};

let s: usize = if start <= 0 { 0 } else { (start - 1) as usize };
let e = match count {
Some(v) => {
if (start - 1 + v) < 0 {
0
} else if (start - 1 + v) <= string.len() as i64 {
(start - 1 + v) as usize
} else {
string.len()
}
}
None => string.len(),
};

let result = if s >= string.len() {
String::from("")
} else {
String::from(&string[s..e])
};
Copy link
Member

Choose a reason for hiding this comment

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

Ok, here type casting to usize looks inevitable.
However, we can simplify the codes by doing a bit of rearrangement.
std::cmp::{min, max} would be useful.

let start = match eval_to_integer(start).await? {                             
    Nullable::Value(v) => v - 1,                                              
    Nullable::Null => {                                                       
        return Ok(Evaluated::from(Value::Null));                              
    }                                                                         
};                                                                            
                                                                              
let end = match count {                                                       
    Some(expr) => match eval_to_integer(expr).await? {                        
        Nullable::Value(count) => {                                           
            if count < 0 {                                                    
                return Err(EvaluateError::NegativeSubstrLenNotAllowed.into());
            }                                                                 
                                                                              
            min(max(start + count, 0) as usize, string.len())                 
        }                                                                     
        Nullable::Null => {                                                   
            return Ok(Evaluated::from(Value::Null));                          
        }                                                                     
    },                                                                        
    None => string.len(),                                                     
};                                                                            
                                                                              
let start = min(max(start, 0) as usize, string.len());                        
let string = String::from(&string[start..end]);                               

Copy link
Member

Choose a reason for hiding this comment

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

I mainly focused on two points.

  1. handling start codes were splitted into two.
let s: usize = if start <= 0 { 0 } else { (start - 1) as usize };
let result = if s >= string.len() {
    String::from("")
} else {
    String::from(&string[s..e])
};
  1. Also the count handling match codes
let count = match count {
    Some(expr) => match eval_to_integer(expr).await? {
         ..
let e = match count {
    Some(v) => {
        if (start - 1 + v) < 0 {
    ..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for kindly telling me how to refactor.:innocent:
I couldn't think I could turn it into a simple code like you wrote.
I think I got a little closer to rust thanks to your help 😋

@vbbono
Copy link
Contributor Author

vbbono commented Sep 15, 2021

I think this PR is ready for re-review 🤣 Please take a look. Thanks !

@panarch panarch self-requested a review September 15, 2021 11:54
Copy link
Member

@panarch panarch left a comment

Choose a reason for hiding this comment

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

Awesome, thanks a lot! merging 👍 👍 👍

@panarch panarch merged commit 393cdad into gluesql:main Sep 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Implement Function: SUBSTR
3 participants