Skip to content

feat(cli): add sql_delimiter set #649

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

Merged
merged 4 commits into from
Jul 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ prompt = ":) "
| `expand` | Expand table format display, default auto, could be on/off/auto. |
| `time` | Whether to show the time elapsed when executing queries. |
| `multi_line` | Whether to allow multi-line input. |
| `quote_string` | Whether to quote string values in table output format. |
| `quote_string` | Whether to quote string values in table output format, default false. |
| `sql_delimiter` | SQL delimiter, default `;`. |

## Commands in REPL

Expand Down
12 changes: 11 additions & 1 deletion cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub struct SettingsConfig {
pub show_stats: Option<bool>,
pub expand: Option<String>,
pub quote_string: Option<bool>,
pub sql_delimiter: Option<char>,
pub max_display_rows: Option<usize>,
pub max_col_width: Option<usize>,
pub max_width: Option<usize>,
Expand Down Expand Up @@ -101,6 +102,7 @@ pub struct Settings {
pub multi_line: bool,
/// Whether to quote string values in table output format.
pub quote_string: bool,
pub sql_delimiter: char,

pub bind_address: String,
pub bind_port: u16,
Expand Down Expand Up @@ -157,6 +159,7 @@ impl Settings {
.map(|expand| expand.as_str().into())
.unwrap_or_else(|| self.expand);
self.quote_string = cfg.quote_string.unwrap_or(self.quote_string);
self.sql_delimiter = cfg.sql_delimiter.unwrap_or(self.sql_delimiter);
self.max_width = cfg.max_width.unwrap_or(self.max_width);
self.max_col_width = cfg.max_col_width.unwrap_or(self.max_col_width);
self.max_display_rows = cfg.max_display_rows.unwrap_or(self.max_display_rows);
Expand Down Expand Up @@ -197,6 +200,12 @@ impl Settings {
"max_width" => self.max_width = cmd_value.parse()?,
"max_col_width" => self.max_col_width = cmd_value.parse()?,
"quote_string" => self.quote_string = cmd_value.parse()?,
"sql_delimiter" => {
if cmd_value.len() != 1 {
return Err(anyhow!("SQL delimiter must be a single character"));
}
self.sql_delimiter = cmd_value.chars().next().unwrap()
}
_ => return Err(anyhow!("Unknown command: {}", cmd_name)),
}
Ok(())
Expand Down Expand Up @@ -261,6 +270,7 @@ impl Default for Settings {
no_auto_complete: false,
output_format: OutputFormat::Table,
quote_style: OutputQuoteStyle::Necessary,
sql_delimiter: ';',
expand: ExpandMode::Auto,
show_progress: false,
max_display_rows: 1000,
Expand All @@ -269,7 +279,7 @@ impl Default for Settings {
show_stats: false,
time: None,
multi_line: true,
quote_string: true,
quote_string: false,
auto_open_browser: false,
bind_address: "127.0.0.1".to_string(),
bind_port: 0,
Expand Down
29 changes: 20 additions & 9 deletions cli/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ impl Session {
}
self.query.push_str(line);
let mut err = String::new();
let delimiter = self.settings.sql_delimiter;

'Parser: loop {
let mut is_valid = true;
Expand All @@ -493,7 +494,7 @@ impl Session {
match token {
Ok(token) => {
// SQL end with `;` or `\G` in repl
let is_end_query = matches!(token.kind, TokenKind::SemiColon);
let is_end_query = token.text() == delimiter.to_string();
let is_slash_g = self.is_repl
&& (previous_token_backslash
&& token.kind == TokenKind::Ident
Expand All @@ -503,8 +504,8 @@ impl Session {
if is_end_query || is_slash_g {
// push to current and continue the tokenizer
let (sql, remain) = self.query.split_at(token.span.end as usize);
if is_valid && !sql.is_empty() && sql.trim() != ";" {
queries.push(sql.to_string());
if is_valid && !sql.is_empty() && sql.trim() != delimiter.to_string() {
queries.push(sql.trim_end_matches(delimiter).to_string());
}
self.query = remain.to_string();
continue 'Parser;
Expand Down Expand Up @@ -532,15 +533,17 @@ impl Session {
pub async fn handle_query(
&mut self,
is_repl: bool,
query: &str,
raw_query: &str,
) -> Result<Option<ServerStats>> {
let mut query = query.trim_end_matches(';').trim();
let mut query = raw_query
.trim_end_matches(self.settings.sql_delimiter)
.trim();
let mut expand = None;
self.interrupted.store(false, Ordering::SeqCst);

if is_repl {
if query.starts_with('!') {
return self.handle_commands(query).await;
return self.handle_commands(raw_query).await;
}
if query == "exit" || query == "quit" {
return Ok(None);
Expand Down Expand Up @@ -601,12 +604,20 @@ impl Session {
other => {
if other.starts_with("!set") {
let query = query[4..].split_whitespace().collect::<Vec<_>>();
if query.len() != 2 {
if query.len() == 3 {
if query[1] != "=" {
return Err(anyhow!(
"Control command error, must be syntax of `!set cmd_name = cmd_value`."
));
}
self.settings.inject_ctrl_cmd(query[0], query[2])?;
} else if query.len() == 2 {
self.settings.inject_ctrl_cmd(query[0], query[1])?;
} else {
return Err(anyhow!(
"Control command error, must be syntax of `.cmd_name cmd_value`."
"Control command error, must be syntax of `!set cmd_name = cmd_value` or `!set cmd_name cmd_value`."
));
}
self.settings.inject_ctrl_cmd(query[0], query[1])?;
} else if other.starts_with("!source") {
let query = query[7..].trim();
let path = Path::new(query);
Expand Down
10 changes: 10 additions & 0 deletions cli/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ DATABEND_HOST=${DATABEND_HOST:-localhost}
DATABEND_PORT=${DATABEND_PORT:-8000}

TEST_HANDLER=$1
FILTER=$2

cargo build --bin bendsql

Expand All @@ -44,6 +45,10 @@ esac
export BENDSQL="${CARGO_TARGET_DIR}/debug/bendsql"

for tf in cli/tests/*.{sql,sh}; do
if [[ -n "$FILTER" && ! $tf =~ $FILTER ]]; then
continue
fi

[[ -e "$tf" ]] || continue
echo " Running test -- ${tf}"
if [[ $tf == *.sh ]]; then
Expand All @@ -59,6 +64,11 @@ rm -f cli/tests/*.output

for tf in cli/tests/"$TEST_HANDLER"/*.{sql,sh}; do
[[ -e "$tf" ]] || continue

if [[ -n "$FILTER" && ! $tf =~ $FILTER ]]; then
continue
fi

echo " Running test -- ${tf}"
if [[ $tf == *.sh ]]; then
suite=$(basename "${tf}" | sed -e 's#.sh##')
Expand Down
3 changes: 3 additions & 0 deletions cli/tests/00-base.result
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Parser 'CREATE TABLE 🐳🍞(🐳🐳 INTEGER, 🍞🍞 INTEGER);' failed
with error 'unable to recognize the rest tokens'
Parser 'explain select * from c where b in ('x');' failed
with error 'unable to recognize the rest tokens'
aa
"def add(a, b):
a + b"
3.00 3.00 0.0000000170141183460469231731687303715884105727000 -0.0000000170141183460469231731687303715884105727000
Asia/Shanghai
3
Expand Down
13 changes: 7 additions & 6 deletions cli/tests/00-base.sql
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ CREATE TABLE 🐳🍞(🐳🐳 INTEGER, 🍞🍞 INTEGER);
explain select * from c where b in ('x');

-- enable it after we support code string in databend
-- select $$aa$$;
-- select $$
-- def add(a, b):
-- a + b
-- $$;
select $$aa$$;
select $$
def add(a, b):
a + b
$$;

/* ignore this block /* /*
/* ignore this block
select 'in comment block';
*/

Expand All @@ -68,3 +68,4 @@ select 'bye';
drop table test;
drop table test_decimal;
drop table test_nested;

Loading