-
Notifications
You must be signed in to change notification settings - Fork 129
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
Displayer for QueryRowsResult #1138
base: main
Are you sure you want to change the base?
Conversation
c8ad86d
to
ddf3d5d
Compare
3a3f682
to
e15634e
Compare
e15634e
to
e822850
Compare
|
54ad40d
to
7b10b8b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯 This is a really great job done! In the aspect of usability, I'm very satisfied.
🔧 There are a few items to be addressed, though, mainly about:
- efficiency: I believe we can get rid of String allocations on the way to display,
- more idiomatic code,
- division into modules.
scylla/src/response/query_result.rs
Outdated
} | ||
} | ||
|
||
/// A utility struct for displaying rows received from the database in a [`QueryRowsResult`]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📌 As I noted elsewhere, we should consider making this work both with QueryRowsResult
and with TypedRowStream
.
7b10b8b
to
7687302
Compare
222febc
to
549fd82
Compare
impl fmt::Display for WrapperDisplay<'_, CqlDate> { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
// example of formating date 2021-12-31 | ||
let magic_constant = 2055453495; // it is number of days from -5877641-06-23 to -250000-01-01 | ||
// around -250000-01-01 is the limit of naive date | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Please explain in detail what the "magic constant" is and why it is correct in your opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it is correct because the dates printed by the displayer are the same as those inserted into the database, this constant allows shifting the date calculation to a valid range, thus preventing from_ymd_opt()
from returning None
. The date -5877641-06-23 is the reference point for CqlDate
.
549fd82
to
9b05d0e
Compare
rebased on main |
addressed comments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯 Looks good!
Could you please squash the 3 latter commits into the respective former 2? So that nothing is introduced in a bad way in one commit only to be fixed in another.
🔧 There are new merge conflicts; when squashing the commits I asked you for, please remember to rebase on main. |
369b40c
to
20d1534
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯 🎉 Looks good to me! Thank you for the contribution!
57d9a2f
to
475ab0d
Compare
@Michu1596 Thanks for the screenshots comparing the display of cqlsh and your displayer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code desperately needs:
- Tests
- Proper commit messages and cover letter
I am sure writing this needed a lot of design decisions. Those design decisions and limitations should be all described in PR and commits. I managed to decipher some of the approaches, but not all, and I have no idea how correct I am. I also should not have to guess why the code is written the way it is - it should be explained.
I managed to get through some of the changes. I'll go through the rest after proper descriptions and tests are added.
|
||
|
||
[[example]] | ||
name = "displayer" | ||
path = "displayer.rs" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⛏ Missing newline
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's nice that you added a new example for the displayer.
Do you think you could modify our cqlsh-rs
example to utilize the new displayer, instead of using its simplistic print_result
function?
// use of QueryRowsResult after use of displayer | ||
#[cfg(feature = "result-displayer")] | ||
{ | ||
let rr = sample_raw_rows(2, 1); | ||
let rqr = QueryResult::new(Some(rr), None, Vec::new()); | ||
let qr: QueryRowsResult = rqr.into_rows_result().unwrap(); | ||
let mut displayer = qr.rows_displayer(); | ||
displayer.set_terminal_width(80); | ||
displayer.set_blob_displaying(ByteDisplaying::Hex); | ||
displayer.use_color(true); | ||
let _ = format!("{}", displayer); | ||
let rows = qr.rows::<(&str, bool)>(); | ||
|
||
let mut rows_data = rows.unwrap(); | ||
let row = rows_data.next().unwrap().unwrap(); | ||
|
||
assert_eq!(row, ("MOCK", true)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is already gigantic, can you put it in a separate test?
If you put the tests in the rows_displayer
module, you won't need to guard it with cfg
.
(@wprzytula is there a reason for this being a 300 lines test instead of few smaller ones? test cases in it seem independent at the first glance).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm.... is this the only test for this feature? We definitely need more.
Check out history tests (history.rs
file) - it is also a module that produces text output, and has tests for its correctness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(btw @wprzytula is there a reason for this being a 300 lines test instead of few smaller ones? test cases in it seem independent at the first glance).
It was easier to be written as a single test, just that.
/// Sets the terminal width for wrapping the table output. | ||
/// The table will be wrapped to the given width, if possible. | ||
/// If the width is set to 0, the table will not be wrapped. | ||
/// The default value is 0. | ||
pub fn set_terminal_width(&mut self, terminal_width: usize) { | ||
self.display_settings.terminal_width = terminal_width; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of special value (0) this method could accept Option
, with None indicating that wrapping should be disabled. It's a more Rusty way.
/// Sets the exponent display for integers. | ||
/// If set to true, integers will be displayed in scientific notation. | ||
/// The default value is false. | ||
pub fn set_exponent_displaying_integers(&mut self, exponent_displaying_integers: bool) { | ||
self.display_settings.exponent_displaying_integers = exponent_displaying_integers; | ||
} | ||
|
||
/// Sets the exponent display for floats and doubles. | ||
pub fn set_floating_point_precision(&mut self, precision: usize) { | ||
self.display_settings.double_precision = precision; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment on the second function seems to not describe what the function really does. copypaste error?
Also, please add the function that actually sets exponent displaying for floating point numbers - I see there is such a field in RowsDisplayerSettings
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great example of why tests are needed.
for byte in self.value { | ||
match self.settings.byte_displaying { | ||
ByteDisplaying::Ascii => { | ||
write_colored!( | ||
f, | ||
self.settings.print_in_color, | ||
self.settings.colors.blob, | ||
"{}", | ||
*byte as char | ||
)?; | ||
} | ||
ByteDisplaying::Hex => { | ||
write_colored!( | ||
f, | ||
self.settings.print_in_color, | ||
self.settings.colors.blob, | ||
"{:02x}", | ||
byte | ||
)?; | ||
} | ||
ByteDisplaying::Dec => { | ||
write_colored!( | ||
f, | ||
self.settings.print_in_color, | ||
self.settings.colors.blob, | ||
"{}", | ||
byte | ||
)?; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wow. Here you call write_colored! for every single byte.
This macro will write, apart from the byte itself, 19 other bytes of ansi escape codes.
Please fix this. Write ascii escape codes once, then write the buffer, and then the closing ascii codes.
ByteDisplaying::Dec => { | ||
write_colored!( | ||
f, | ||
self.settings.print_in_color, | ||
self.settings.colors.blob, | ||
"{}", | ||
byte | ||
)?; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will write each byte as decimal, and concatenate this.
This will result in a very unreadable output. Is that what cqlsh does?
Even if it is, we should do it in some actually usable way.
Options: | ||
- `Hex`: Display as hexadecimal values (e.g., "0A FF 42") | ||
- `Ascii`: Display as ASCII characters where possible | ||
- `Dec`: Display as decimal values (e.g., "213 7 66") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that correct? In the implementation I did not see spaces between values. In your screenshot it also looked differently than here.
## Best Practices | ||
|
||
1. **Terminal Width** | ||
- Set appropriate terminal width for better readability | ||
- Consider using terminal width detection if available | ||
- Use 0 width for untruncated output | ||
|
||
2. **Color Usage** | ||
- Enable colors for better type distinction | ||
- Disable colors when outputting to files or non-terminal destinations | ||
- Consider user accessibility settings | ||
|
||
3. **Binary Data** | ||
- Choose appropriate blob display format based on data content | ||
- Use Hex for general binary data | ||
- Use ASCII when data is known to be text | ||
- Use Dec for byte-oriented analysis | ||
|
||
4. **Number Formatting** | ||
- Adjust floating point precision based on data requirements | ||
- Enable scientific notation for very large/small numbers | ||
- Consider locale-specific formatting needs | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this section LLM-generated? It contains a lot of text, yet not a single useful information.
## Implementation Details | ||
|
||
The displayer uses the following traits internally: | ||
- `Display` for converting values to strings | ||
- Custom formatting traits for specific types | ||
|
||
Output is generated using Rust's formatting system (`fmt::Display`), ensuring efficient memory usage and proper error handling. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would a user care about implementation details?
Why are we talking about efficient memory usage if the formatter uses Row
and CqlValue
, allocated all the rows at once, and boxes all the values again?
…qlsh` and examples of usage
475ab0d
to
30b30a3
Compare
This PR adds Displayer for QueryRowsResult trying to mimic
cqlsh
way of displaying tables, but there are some differences:cqlsh
displays this type in an unclear way,for more details look in the comments.list
,map
,set
,tuple
,udt
), I use quotes only fortext
as I think putting other types in quotes might be confusing.Fixes: #999
Pre-review checklist
./docs/source/
.Fixes:
annotations to PR description.