Merge 'Fix SQL comment handling Limbo shell' from Clyde

This PR improves comment handling in Limbo to precisely match SQLite's
behavior:
Fixes some edge cases involving #711
Inline comments mess up queries --
![image](https://github.com/user-
attachments/assets/10a90c39-a9b7-49e4-a018-1489914a5b64)
Query in the left terminal is current limbo state, upper right is limbo
in the state of this PR
and lower right is sqlite behavior.
![image](https://github.com/user-
attachments/assets/2f1b369a-0495-415e-a091-5b6972488f50)
Added support for inline comments using "--" syntax
Comments are now properly stripped before query execution
Maintains correct query execution when comments appear mid-query
Preserves multiline query functionality with comments
Ensures consistent behavior between pasted and typed queries
Testing:
Added test cases for single-line comments
Added test cases for inline comments
Added test cases for multiline queries with comments
Verified behavior matches SQLite CLI
![image](https://github.com/user-
attachments/assets/225772e9-03c4-472d-8a17-0f46e35c34ba)

Reviewed-by: Preston Thorpe (@PThorpe92)

Closes #722
This commit is contained in:
Pekka Enberg
2025-01-18 08:45:57 +02:00
2 changed files with 106 additions and 11 deletions

View File

@@ -434,9 +434,6 @@ impl Limbo {
line: &str,
rl: &mut rustyline::DefaultEditor,
) -> anyhow::Result<()> {
if line.trim_start().starts_with("--") {
return Ok(());
}
if self.input_buff.is_empty() {
if line.is_empty() {
return Ok(());
@@ -448,6 +445,42 @@ impl Limbo {
return Ok(());
}
}
if line.trim_start().starts_with("--") {
if let Some(remaining) = line.split_once('\n') {
let after_comment = remaining.1.trim();
if !after_comment.is_empty() {
rl.add_history_entry(after_comment.to_owned())?;
self.buffer_input(after_comment);
if after_comment.ends_with(';') {
if self.opts.echo {
let _ = self.writeln(after_comment);
}
let conn = self.conn.clone();
let runner = conn.query_runner(after_comment.as_bytes());
for output in runner {
if let Err(e) = self.print_query_result(after_comment, output) {
let _ = self.writeln(e.to_string());
}
}
self.reset_input();
} else {
self.set_multiline_prompt();
}
self.interrupt_count.store(0, Ordering::SeqCst);
return Ok(());
}
}
return Ok(());
}
if let Some(comment_pos) = line.find("--") {
let before_comment = line[..comment_pos].trim();
if !before_comment.is_empty() {
return self.handle_input_line(before_comment, rl);
}
}
if line.ends_with(';') {
self.buffer_input(line);
let buff = self.input_buff.clone();

View File

@@ -52,7 +52,9 @@ def execute_sql(pipe, sql):
output = ""
while True:
ready_to_read, _, error_in_pipe = select.select([stdout, stderr], [], [stdout, stderr])
ready_to_read, _, error_in_pipe = select.select(
[stdout, stderr], [], [stdout, stderr]
)
ready_to_read_or_err = set(ready_to_read + error_in_pipe)
if stderr in ready_to_read_or_err:
exit_on_error(stderr)
@@ -67,6 +69,7 @@ def execute_sql(pipe, sql):
output = strip_each_line(output)
return output
def strip_each_line(lines: str) -> str:
lines = lines.split("\n")
lines = [line.strip() for line in lines if line != ""]
@@ -237,8 +240,10 @@ csv_file = "./test_files/test.csv"
write_to_pipe(".open :memory:")
def test_import_csv(test_name: str, options: str, import_output: str, table_output: str):
csv_table_name = f'csv_table_{test_name}'
def test_import_csv(
test_name: str, options: str, import_output: str, table_output: str
):
csv_table_name = f"csv_table_{test_name}"
write_to_pipe(f"CREATE TABLE {csv_table_name} (c1 INT, c2 REAL, c3 String);")
do_execshell_test(
pipe,
@@ -253,11 +258,15 @@ def test_import_csv(test_name: str, options: str, import_output: str, table_outp
table_output,
)
test_import_csv('no_options', '--csv', '', '1|2.0|String\'1\n3|4.0|String2')
test_import_csv('verbose', '--csv -v',
'Added 2 rows with 0 errors using 2 lines of input'
,'1|2.0|String\'1\n3|4.0|String2')
test_import_csv('skip', '--csv --skip 1', '' ,'3|4.0|String2')
test_import_csv("no_options", "--csv", "", "1|2.0|String'1\n3|4.0|String2")
test_import_csv(
"verbose",
"--csv -v",
"Added 2 rows with 0 errors using 2 lines of input",
"1|2.0|String'1\n3|4.0|String2",
)
test_import_csv("skip", "--csv --skip 1", "", "3|4.0|String2")
# Verify the output file exists and contains expected content
@@ -297,6 +306,59 @@ else:
print(f"File contents:\n{file_contents}")
exit(1)
do_execshell_test(
pipe,
"test-single-line-comment",
"-- this is a comment\nSELECT 1;",
"1",
)
do_execshell_test(
pipe,
"test-multi-line-single-line-comments-in-succession",
"""-- First of the comments
-- Second line of the comments
SELECT 2;""",
"2",
)
do_execshell_test(
pipe,
"test-multi-line-comments",
"""/*
This is a multi-line comment
*/
SELECT 3;""",
"3",
)
# readd some data to test inline comments
write_to_pipe("""
CREATE TABLE users (id INTEGER PRIMARY KEY, first_name TEXT, last_name TEXT, age INTEGER);
INSERT INTO users (id, first_name, last_name, age) VALUES
(1, 'Alice', 'Smith', 30), (2, 'Bob', 'Johnson', 25), (3, 'Charlie', 'Brown', 66), (4, 'David', 'Nichols', 70);
""")
do_execshell_test(
pipe,
"test-inline-comments",
"""SELECT id, -- this is a comment until newline
first_name
FROM users
LIMIT 1; """,
"1|Alice",
)
do_execshell_test(
pipe,
"test-multiple-inline-comments",
"""SELECT id, --first inline
--second inline
first_name
--third inline
FROM users
LIMIT 1; """,
"1|Alice",
)
# Cleanup
os.remove(filepath)
pipe.terminate()