diff --git a/README.md b/README.md index a7193c33..4a03a9d2 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cli/src/config.rs b/cli/src/config.rs index 1b423e36..f0ca847b 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -42,6 +42,7 @@ pub struct SettingsConfig { pub show_stats: Option, pub expand: Option, pub quote_string: Option, + pub sql_delimiter: Option, pub max_display_rows: Option, pub max_col_width: Option, pub max_width: Option, @@ -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, @@ -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); @@ -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(()) @@ -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, @@ -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, diff --git a/cli/src/session.rs b/cli/src/session.rs index 2b26e000..911b4517 100644 --- a/cli/src/session.rs +++ b/cli/src/session.rs @@ -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; @@ -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 @@ -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; @@ -532,15 +533,17 @@ impl Session { pub async fn handle_query( &mut self, is_repl: bool, - query: &str, + raw_query: &str, ) -> Result> { - 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); @@ -601,12 +604,20 @@ impl Session { other => { if other.starts_with("!set") { let query = query[4..].split_whitespace().collect::>(); - 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); diff --git a/cli/test.sh b/cli/test.sh index 4cd849fe..88722b7f 100755 --- a/cli/test.sh +++ b/cli/test.sh @@ -23,6 +23,7 @@ DATABEND_HOST=${DATABEND_HOST:-localhost} DATABEND_PORT=${DATABEND_PORT:-8000} TEST_HANDLER=$1 +FILTER=$2 cargo build --bin bendsql @@ -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 @@ -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##') diff --git a/cli/tests/00-base.result b/cli/tests/00-base.result index 3d9fc6ee..0709bb3a 100644 --- a/cli/tests/00-base.result +++ b/cli/tests/00-base.result @@ -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 diff --git a/cli/tests/00-base.sql b/cli/tests/00-base.sql index e17aa417..cc7594c2 100644 --- a/cli/tests/00-base.sql +++ b/cli/tests/00-base.sql @@ -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'; */ @@ -68,3 +68,4 @@ select 'bye'; drop table test; drop table test_decimal; drop table test_nested; +