1use std::collections::HashMap;
19use std::sync::Arc;
20
21use common_query::Output;
22use common_recordbatch::RecordBatches;
23use common_time::timezone::system_timezone_name;
24use common_version;
25use datatypes::prelude::ConcreteDataType;
26use datatypes::schema::{ColumnSchema, Schema};
27use datatypes::vectors::StringVector;
28use once_cell::sync::Lazy;
29use regex::Regex;
30use regex::bytes::RegexSet;
31use session::SessionRef;
32use session::context::QueryContextRef;
33
34const VARIABLES_SCOPE: &str = "(GLOBAL |SESSION |LOCAL )?";
36
37static SELECT_VAR_PATTERN: Lazy<Regex> =
38 Lazy::new(|| Regex::new("(?i)^(SELECT\\s+@@(.*))").unwrap());
39static SHOW_LOWER_CASE_PATTERN: Lazy<Regex> = Lazy::new(|| {
40 Regex::new(&format!(
41 "(?i)^(SHOW {VARIABLES_SCOPE}VARIABLES LIKE 'lower_case_table_names'(.*))"
42 ))
43 .unwrap()
44});
45static SHOW_VARIABLES_LIKE_PATTERN: Lazy<Regex> = Lazy::new(|| {
46 Regex::new(&format!(
47 "(?i)^(SHOW {VARIABLES_SCOPE}VARIABLES( LIKE (.*))?)"
48 ))
49 .unwrap()
50});
51static SHOW_WARNINGS_PATTERN: Lazy<Regex> =
52 Lazy::new(|| Regex::new("(?i)^(SHOW WARNINGS)").unwrap());
53
54static SELECT_USER_OR_VAR_PATTERN: Lazy<Regex> = Lazy::new(|| {
58 Regex::new(
59 "(?i)^SELECT\\s+(?:(CURRENT_USER|SESSION_USER|SYSTEM_USER|USER)|(@[a-z0-9_$.]+))\\s*;?\\s*$",
60 )
61 .unwrap()
62});
63
64static SELECT_TIME_DIFF_FUNC_PATTERN: Lazy<Regex> =
66 Lazy::new(|| Regex::new("(?i)^(SELECT TIMEDIFF\\(NOW\\(\\), UTC_TIMESTAMP\\(\\)\\))").unwrap());
67
68static SHOW_SQL_MODE_PATTERN: Lazy<Regex> = Lazy::new(|| {
70 Regex::new(&format!(
71 "(?i)^(SHOW {VARIABLES_SCOPE}VARIABLES LIKE 'sql_mode'(.*))"
72 ))
73 .unwrap()
74});
75
76static OTHER_NOT_SUPPORTED_STMT: Lazy<RegexSet> = Lazy::new(|| {
77 RegexSet::new([
78 "(?i)^(ROLLBACK(.*))",
80 "(?i)^(COMMIT(.*))",
81 "(?i)^(START(.*))",
82 "(?i)^(BEGIN(.*))",
83
84 "(?i)^(SET NAMES(.*))",
86 "(?i)^(SET character_set_results(.*))",
87 "(?i)^(SET net_write_timeout(.*))",
88 "(?i)^(SET FOREIGN_KEY_CHECKS(.*))",
89 "(?i)^(SET AUTOCOMMIT(.*))",
90 "(?i)^(SET SQL_LOG_BIN(.*))",
91 "(?i)^(SET SESSION TRANSACTION(.*))",
92 "(?i)^(SET TRANSACTION(.*))",
93 "(?i)^(SET sql_mode(.*))",
94 "(?i)^(SET SQL_SELECT_LIMIT(.*))",
95 "(?i)^(SET PROFILING(.*))",
96
97 "(?i)^(SELECT \\$\\$)",
99
100 "(?i)^(SET SQL_QUOTE_SHOW_CREATE(.*))",
102 "(?i)^(LOCK TABLES(.*))",
103 "(?i)^(UNLOCK TABLES(.*))",
104 "(?i)^(SELECT LOGFILE_GROUP_NAME, FILE_NAME, TOTAL_EXTENTS, INITIAL_SIZE, ENGINE, EXTRA FROM INFORMATION_SCHEMA.FILES(.*))",
105
106 "(?i)^(/\\*!80003 SET(.*) \\*/)$",
108 "(?i)^(SHOW MASTER STATUS)",
109 "(?i)^(SHOW ALL SLAVES STATUS)",
110 "(?i)^(LOCK BINLOG FOR BACKUP)",
111 "(?i)^(LOCK TABLES FOR BACKUP)",
112 "(?i)^(UNLOCK BINLOG(.*))",
113 "(?i)^(/\\*!40101 SET(.*) \\*/)$",
114
115 "(?i)^(SHOW PLUGINS)",
117 "(?i)^(SHOW ENGINES)",
118 "(?i)^(SHOW @@(.*))",
119
120 "(?i)^(/\\*!40101 SET(.*) \\*/)$",
122
123 "(?i)^(/\\*!40100 SET(.*) \\*/)$",
125 "(?i)^(/\\*!40103 SET(.*) \\*/)$",
126 "(?i)^(/\\*!40111 SET(.*) \\*/)$",
127 "(?i)^(/\\*!40101 SET(.*) \\*/)$",
128 "(?i)^(/\\*!40014 SET(.*) \\*/)$",
129 "(?i)^(/\\*!40000 SET(.*) \\*/)$",
130 ]).unwrap()
131});
132
133static VAR_VALUES: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
134 HashMap::from([
135 ("tx_isolation", "REPEATABLE-READ"),
136 ("session.tx_isolation", "REPEATABLE-READ"),
137 ("transaction_isolation", "REPEATABLE-READ"),
138 ("session.transaction_isolation", "REPEATABLE-READ"),
139 ("session.transaction_read_only", "0"),
140 ("max_allowed_packet", "134217728"),
141 ("interactive_timeout", "31536000"),
142 ("wait_timeout", "31536000"),
143 ("net_write_timeout", "31536000"),
144 ("version_comment", common_version::product_name()),
145 ])
146});
147
148fn select_function(name: &str, value: Option<&str>) -> RecordBatches {
153 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
154 name,
155 ConcreteDataType::string_datatype(),
156 true,
157 )]));
158 let columns = vec![Arc::new(StringVector::from(vec![value])) as _];
159 RecordBatches::try_from_columns(schema, columns)
160 .unwrap()
162}
163
164fn show_variables(name: &str, value: &str) -> RecordBatches {
169 let schema = Arc::new(Schema::new(vec![
170 ColumnSchema::new("Variable_name", ConcreteDataType::string_datatype(), true),
171 ColumnSchema::new("Value", ConcreteDataType::string_datatype(), true),
172 ]));
173 let columns = vec![
174 Arc::new(StringVector::from(vec![name])) as _,
175 Arc::new(StringVector::from(vec![value])) as _,
176 ];
177 RecordBatches::try_from_columns(schema, columns)
178 .unwrap()
180}
181
182fn select_variable(query: &str, query_context: QueryContextRef) -> Option<Output> {
183 let mut fields = vec![];
184 let mut values = vec![];
185
186 let query = query.to_lowercase();
188 let vars: Vec<&str> = query.split("@@").collect();
189 if vars.len() <= 1 {
190 return None;
191 }
192
193 for var in vars.iter().skip(1) {
195 let var = var.trim_matches(|c| c == ' ' || c == ',' || c == ';');
196 let var_as: Vec<&str> = var
197 .split(" as ")
198 .map(|x| {
199 x.trim_matches(|c| c == ' ')
200 .split_whitespace()
201 .next()
202 .unwrap_or("")
203 })
204 .collect();
205
206 let value = match var_as[0] {
208 "session.time_zone" | "time_zone" => query_context.timezone().to_string(),
209 "system_time_zone" => system_timezone_name(),
210 "max_execution_time" | "session.max_execution_time" => {
211 query_context.query_timeout_as_millis().to_string()
212 }
213 _ => VAR_VALUES
214 .get(var_as[0])
215 .map(|v| v.to_string())
216 .unwrap_or_else(|| "0".to_owned()),
217 };
218
219 values.push(Arc::new(StringVector::from(vec![value])) as _);
220 match var_as.len() {
221 1 => {
222 fields.push(ColumnSchema::new(
225 format!("@@{}", var_as[0]),
226 ConcreteDataType::string_datatype(),
227 true,
228 ));
229 }
230 2 => {
231 fields.push(ColumnSchema::new(
235 var_as[1],
236 ConcreteDataType::string_datatype(),
237 true,
238 ));
239 }
240 _ => return None,
241 }
242 }
243
244 let schema = Arc::new(Schema::new(fields));
245 let batches = RecordBatches::try_from_columns(schema, values).unwrap();
247 Some(Output::new_with_record_batches(batches))
248}
249
250fn check_select_variable(query: &str, query_context: QueryContextRef) -> Option<Output> {
251 if SELECT_VAR_PATTERN.is_match(query) {
252 select_variable(query, query_context)
253 } else {
254 None
255 }
256}
257
258fn check_select_user_or_var(query: &str, query_context: QueryContextRef) -> Option<Output> {
259 let captures = SELECT_USER_OR_VAR_PATTERN.captures(query)?;
260
261 let recordbatches = if let Some(keyword) = captures.get(1) {
262 let user = query_context.current_user();
263 select_function(keyword.as_str(), Some(user.username()))
264 } else {
265 let var = captures
268 .get(2)
269 .expect("one of the two groups always matches");
270 select_function(var.as_str(), None)
271 };
272 Some(Output::new_with_record_batches(recordbatches))
273}
274
275fn check_show_variables(query: &str) -> Option<Output> {
276 let recordbatches = if SHOW_SQL_MODE_PATTERN.is_match(query) {
277 Some(show_variables(
278 "sql_mode",
279 "ONLY_FULL_GROUP_BY STRICT_TRANS_TABLES NO_ZERO_IN_DATE NO_ZERO_DATE ERROR_FOR_DIVISION_BY_ZERO NO_ENGINE_SUBSTITUTION",
280 ))
281 } else if SHOW_LOWER_CASE_PATTERN.is_match(query) {
282 Some(show_variables("lower_case_table_names", "0"))
283 } else if let Some(matched) = SHOW_VARIABLES_LIKE_PATTERN.find(query) {
284 let mut remainder = strip_leading_comments(&query[matched.end()..]);
285 while let Some(rest) = remainder.strip_prefix(';') {
286 remainder = strip_leading_comments(rest);
287 }
288 if remainder.is_empty() {
289 Some(show_variables("", ""))
290 } else {
291 let remainder = remainder.trim_start();
292 let looks_like_filter = ["WHERE", "LIKE"].iter().any(|keyword| {
294 remainder
295 .get(..keyword.len())
296 .is_some_and(|prefix| prefix.eq_ignore_ascii_case(keyword))
297 && remainder[keyword.len()..]
298 .chars()
299 .next()
300 .is_none_or(|c| !(c == '_' || c == '$' || c.is_alphanumeric()))
301 });
302 if looks_like_filter {
303 Some(show_variables("", ""))
304 } else {
305 None
306 }
307 }
308 } else {
309 None
310 };
311 recordbatches.map(Output::new_with_record_batches)
312}
313
314fn show_warnings(session: &SessionRef) -> RecordBatches {
316 let schema = Arc::new(Schema::new(vec![
317 ColumnSchema::new("Level", ConcreteDataType::string_datatype(), false),
318 ColumnSchema::new("Code", ConcreteDataType::uint16_datatype(), false),
319 ColumnSchema::new("Message", ConcreteDataType::string_datatype(), false),
320 ]));
321
322 let warnings = session.warnings();
323 let count = warnings.len();
324
325 let columns = if count > 0 {
326 vec![
327 Arc::new(StringVector::from(vec!["Warning"; count])) as _,
328 Arc::new(datatypes::vectors::UInt16Vector::from(vec![
329 Some(1000u16);
330 count
331 ])) as _,
332 Arc::new(StringVector::from(warnings)) as _,
333 ]
334 } else {
335 vec![
336 Arc::new(StringVector::from(Vec::<String>::new())) as _,
337 Arc::new(datatypes::vectors::UInt16Vector::from(
338 Vec::<Option<u16>>::new(),
339 )) as _,
340 Arc::new(StringVector::from(Vec::<String>::new())) as _,
341 ]
342 };
343
344 RecordBatches::try_from_columns(schema, columns).unwrap()
345}
346
347fn check_show_warnings(query: &str, session: &SessionRef) -> Option<Output> {
348 if SHOW_WARNINGS_PATTERN.is_match(query) {
349 Some(Output::new_with_record_batches(show_warnings(session)))
350 } else {
351 None
352 }
353}
354
355fn check_others(query: &str, _query_ctx: QueryContextRef) -> Option<Output> {
357 if OTHER_NOT_SUPPORTED_STMT.is_match(query.as_bytes()) {
358 return Some(Output::new_with_record_batches(RecordBatches::empty()));
359 }
360
361 let recordbatches = if SELECT_TIME_DIFF_FUNC_PATTERN.is_match(query) {
362 Some(select_function(
363 "TIMEDIFF(NOW(), UTC_TIMESTAMP())",
364 Some("00:00:00"),
365 ))
366 } else {
367 None
368 };
369 recordbatches.map(Output::new_with_record_batches)
370}
371
372fn strip_leading_comments(query: &str) -> &str {
379 let mut rest = query.trim_start();
380 loop {
381 if rest.starts_with("/*!") {
385 return rest;
386 }
387 if let Some(tail) = rest.strip_prefix("/*") {
388 let Some(end) = tail.find("*/") else {
390 return "";
391 };
392 rest = tail[end + 2..].trim_start();
393 } else if rest.starts_with('#')
394 || (rest.starts_with("--")
396 && rest[2..].chars().next().is_none_or(|c| c.is_whitespace()))
397 {
398 let Some(end) = rest.find('\n') else {
399 return "";
400 };
401 rest = rest[end + 1..].trim_start();
402 } else {
403 return rest;
404 }
405 }
406}
407
408const OTHER_LEADING_KEYWORDS: [&str; 7] = [
414 "SET", "COMMIT", "ROLLBACK", "START", "BEGIN", "LOCK", "UNLOCK",
415];
416
417fn leading_keyword(query: &str) -> &str {
420 let end = query
421 .find(|c: char| !c.is_ascii_alphabetic())
422 .unwrap_or(query.len());
423 &query[..end]
424}
425
426fn line_comment_end(bytes: &[u8], from: usize) -> usize {
428 bytes[from..]
429 .iter()
430 .position(|c| *c == b'\n')
431 .map_or(bytes.len(), |p| from + p + 1)
432}
433
434fn quoted_end(bytes: &[u8], start: usize, quote: u8) -> usize {
436 let mut i = start + 1;
437 while i < bytes.len() {
438 match bytes[i] {
439 b'\\' if quote != b'`' => i += 2,
441 c if c == quote => {
442 if bytes.get(i + 1) == Some("e) {
444 i += 2;
445 } else {
446 return i + 1;
447 }
448 }
449 _ => i += 1,
450 }
451 }
452 bytes.len()
453}
454
455fn has_trailing_statement(query: &str) -> bool {
473 let bytes = query.as_bytes();
474 let mut i = 0;
475 let mut seen_semicolon = false;
476 let mut seen_content = false;
477
478 while i < bytes.len() {
481 match bytes[i] {
482 b'/' if bytes.get(i + 1) == Some(&b'*') => {
483 if bytes.get(i + 2) == Some(&b'!') && seen_content {
484 return true;
485 }
486 i = match bytes[i + 2..].windows(2).position(|w| w == b"*/") {
487 Some(p) => i + 2 + p + 2,
488 None => bytes.len(),
490 };
491 seen_content = true;
492 }
493 b'#' => {
494 i = line_comment_end(bytes, i);
495 seen_content = true;
496 }
497 b'-' if bytes.get(i + 1) == Some(&b'-')
499 && bytes.get(i + 2).is_none_or(|c| c.is_ascii_whitespace()) =>
500 {
501 i = line_comment_end(bytes, i);
502 seen_content = true;
503 }
504 quote @ (b'\'' | b'"' | b'`') => {
505 if seen_semicolon {
506 return true;
507 }
508 seen_content = true;
509 i = quoted_end(bytes, i, quote);
510 }
511 b';' => {
512 seen_semicolon = true;
513 seen_content = true;
514 i += 1;
515 }
516 c if c.is_ascii_whitespace() => i += 1,
517 _ => {
518 if seen_semicolon {
519 return true;
520 }
521 seen_content = true;
522 i += 1;
523 }
524 }
525 }
526
527 false
528}
529
530pub(crate) fn check(
533 query: &str,
534 query_ctx: QueryContextRef,
535 session: SessionRef,
536) -> Option<Output> {
537 let query = strip_leading_comments(query);
538 let keyword = leading_keyword(query);
539
540 let absorbed = if keyword.eq_ignore_ascii_case("SELECT") {
543 check_select_variable(query, query_ctx.clone())
545 .or_else(|| check_select_user_or_var(query, query_ctx.clone()))
546 .or_else(|| check_others(query, query_ctx))
547 } else if keyword.eq_ignore_ascii_case("SHOW") {
548 check_show_variables(query)
549 .or_else(|| check_show_warnings(query, &session))
550 .or_else(|| check_others(query, query_ctx))
551 } else if query.starts_with("/*!")
552 || OTHER_LEADING_KEYWORDS
553 .iter()
554 .any(|k| k.eq_ignore_ascii_case(keyword))
555 {
556 check_others(query, query_ctx)
557 } else {
558 return None;
559 };
560
561 if absorbed.is_some() && has_trailing_statement(query) {
564 return None;
565 }
566
567 absorbed
568}
569
570#[cfg(test)]
571mod test {
572
573 use common_query::OutputData;
574 use common_time::timezone::set_default_timezone;
575 use session::Session;
576 use session::context::{Channel, QueryContext};
577
578 use super::*;
579
580 #[test]
581 fn test_show_variables_where_filter_is_absorbed() {
582 let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0));
586 let output = check(
587 "show variables where Variable_name = 'sql_mode'",
588 QueryContext::arc(),
589 session,
590 )
591 .expect("WHERE-filtered SHOW VARIABLES should be absorbed");
592 assert!(matches!(output.data, OutputData::RecordBatches(_)));
593 }
594
595 #[test]
596 fn test_show_variables_named_variable_falls_back() {
597 let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0));
600 assert!(
601 check(
602 "show variables max_execution_time",
603 QueryContext::arc(),
604 session.clone()
605 )
606 .is_none()
607 );
608 assert!(
609 check(
610 "show variables query.parallelism",
611 QueryContext::arc(),
612 session
613 )
614 .is_none()
615 );
616 }
617
618 #[test]
619 fn test_check_abnormal() {
620 let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0));
621 let query = "🫣一点不正常的东西🫣";
622 let output = check(query, QueryContext::arc(), session.clone());
623
624 assert!(output.is_none());
625 }
626
627 #[test]
628 fn test_check() {
629 let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0));
630 let query = "select 1";
631 let result = check(query, QueryContext::arc(), session.clone());
632 assert!(result.is_none());
633
634 let query = "select version";
635 let output = check(query, QueryContext::arc(), session.clone());
636 assert!(output.is_none());
637
638 fn test(query: &str, expected: &str) {
639 let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0));
640 let output = check(query, QueryContext::arc(), session.clone());
641 match output.unwrap().data {
642 OutputData::RecordBatches(r) => {
643 assert_eq!(&r.pretty_print().unwrap(), expected)
644 }
645 _ => unreachable!(),
646 }
647 }
648
649 let query = "SELECT @@version_comment LIMIT 1";
650 let expected = "\
651+-------------------+
652| @@version_comment |
653+-------------------+
654| GreptimeDB |
655+-------------------+";
656 test(query, expected);
657
658 let query = "select @@tx_isolation, @@session.tx_isolation";
660 let expected = "\
661+-----------------+------------------------+
662| @@tx_isolation | @@session.tx_isolation |
663+-----------------+------------------------+
664| REPEATABLE-READ | REPEATABLE-READ |
665+-----------------+------------------------+";
666 test(query, expected);
667
668 set_default_timezone(Some("Asia/Shanghai")).unwrap();
670 let query = "/* mysql-connector-java-8.0.17 (Revision: 16a712ddb3f826a1933ab42b0039f7fb9eebc6ec) */SELECT @@session.auto_increment_increment AS auto_increment_increment, @@character_set_client AS character_set_client, @@character_set_connection AS character_set_connection, @@character_set_results AS character_set_results, @@character_set_server AS character_set_server, @@collation_server AS collation_server, @@collation_connection AS collation_connection, @@init_connect AS init_connect, @@interactive_timeout AS interactive_timeout, @@license AS license, @@lower_case_table_names AS lower_case_table_names, @@max_allowed_packet AS max_allowed_packet, @@net_write_timeout AS net_write_timeout, @@performance_schema AS performance_schema, @@sql_mode AS sql_mode, @@system_time_zone AS system_time_zone, @@time_zone AS time_zone, @@transaction_isolation AS transaction_isolation, @@wait_timeout AS wait_timeout;";
672 let expected = "\
673+--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+---------------+-----------------------+--------------+
674| auto_increment_increment | character_set_client | character_set_connection | character_set_results | character_set_server | collation_server | collation_connection | init_connect | interactive_timeout | license | lower_case_table_names | max_allowed_packet | net_write_timeout | performance_schema | sql_mode | system_time_zone | time_zone | transaction_isolation | wait_timeout |
675+--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+---------------+-----------------------+--------------+
676| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 31536000 | 0 | 0 | 134217728 | 31536000 | 0 | 0 | Asia/Shanghai | Asia/Shanghai | REPEATABLE-READ | 31536000 |
677+--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+---------------+-----------------------+--------------+";
678 test(query, expected);
679
680 let query = "show variables";
681 let expected = "\
682+---------------+-------+
683| Variable_name | Value |
684+---------------+-------+
685| | |
686+---------------+-------+";
687 test(query, expected);
688
689 let query = "show variables like 'lower_case_table_names'";
690 let expected = "\
691+------------------------+-------+
692| Variable_name | Value |
693+------------------------+-------+
694| lower_case_table_names | 0 |
695+------------------------+-------+";
696 test(query, expected);
697
698 let query = "SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())";
699 let expected = "\
700+----------------------------------+
701| TIMEDIFF(NOW(), UTC_TIMESTAMP()) |
702+----------------------------------+
703| 00:00:00 |
704+----------------------------------+";
705 test(query, expected);
706 }
707
708 #[test]
709 fn test_show_warnings() {
710 let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0));
712 let output = check("SHOW WARNINGS", QueryContext::arc(), session.clone());
713 match output.unwrap().data {
714 OutputData::RecordBatches(r) => {
715 assert_eq!(r.iter().map(|b| b.num_rows()).sum::<usize>(), 0);
716 }
717 _ => unreachable!(),
718 }
719
720 session.add_warning("Test warning message".to_string());
722 let output = check("SHOW WARNINGS", QueryContext::arc(), session.clone());
723 match output.unwrap().data {
724 OutputData::RecordBatches(r) => {
725 let expected = "\
726+---------+------+----------------------+
727| Level | Code | Message |
728+---------+------+----------------------+
729| Warning | 1000 | Test warning message |
730+---------+------+----------------------+";
731 assert_eq!(&r.pretty_print().unwrap(), expected);
732 }
733 _ => unreachable!(),
734 }
735
736 session.clear_warnings();
738 session.add_warning("First warning".to_string());
739 session.add_warning("Second warning".to_string());
740 let output = check("SHOW WARNINGS", QueryContext::arc(), session.clone());
741 match output.unwrap().data {
742 OutputData::RecordBatches(r) => {
743 let expected = "\
744+---------+------+----------------+
745| Level | Code | Message |
746+---------+------+----------------+
747| Warning | 1000 | First warning |
748| Warning | 1000 | Second warning |
749+---------+------+----------------+";
750 assert_eq!(&r.pretty_print().unwrap(), expected);
751 }
752 _ => unreachable!(),
753 }
754
755 let output = check("show warnings", QueryContext::arc(), session.clone());
757 assert!(output.is_some());
758
759 let output = check(
761 "/* ApplicationName=DBeaver */SHOW WARNINGS",
762 QueryContext::arc(),
763 session.clone(),
764 );
765 assert!(output.is_some());
766 }
767
768 #[test]
769 fn test_check_select_user_or_var() {
770 let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0));
771
772 fn pretty(query: &str, session: &SessionRef) -> String {
773 let output = check(query, QueryContext::arc(), session.clone())
774 .unwrap_or_else(|| panic!("{query} was not absorbed"));
775 let OutputData::RecordBatches(batches) = output.data else {
776 unreachable!()
777 };
778 batches.pretty_print().unwrap()
779 }
780
781 assert_eq!(
783 pretty("SELECT CURRENT_USER", &session),
784 "\
785+--------------+
786| CURRENT_USER |
787+--------------+
788| greptime |
789+--------------+"
790 );
791 assert_eq!(
792 pretty("select session_user;", &session),
793 "\
794+--------------+
795| session_user |
796+--------------+
797| greptime |
798+--------------+"
799 );
800
801 assert_eq!(
803 pretty("SELECT @v", &session),
804 "\
805+----+
806| @v |
807+----+
808| |
809+----+"
810 );
811
812 for query in [
815 "SELECT user FROM t",
816 "SELECT current_user, 1",
817 "SELECT @v FROM t",
818 "SELECT @v + 1",
819 "SELECT userid",
820 "SELECT 1",
821 ] {
822 assert!(
823 check(query, QueryContext::arc(), session.clone()).is_none(),
824 "{query} must not be absorbed"
825 );
826 }
827 }
828
829 #[test]
832 fn test_check_skips_multi_statement() {
833 let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0));
834 for query in [
835 "BEGIN; INSERT INTO t VALUES (1); COMMIT",
836 "BEGIN;\nINSERT INTO t VALUES (1)",
837 "START TRANSACTION; DELETE FROM t",
838 "COMMIT; INSERT INTO t VALUES (1)",
839 "SET NAMES utf8mb4; INSERT INTO t VALUES (1)",
840 "SELECT @@version; INSERT INTO t VALUES (1)",
841 ] {
842 assert!(
843 check(query, QueryContext::arc(), session.clone()).is_none(),
844 "{query} must not be absorbed"
845 );
846 }
847
848 for query in [
850 "BEGIN;",
851 "COMMIT; ",
852 "SET NAMES utf8mb4;\n",
853 "BEGIN; -- done",
854 "COMMIT; /* done */",
855 "BEGIN; # done",
856 "BEGIN;;",
857 "COMMIT; ; /* done */ ;",
858 "BEGIN; -- done\n",
859 ] {
860 assert!(
861 check(query, QueryContext::arc(), session.clone()).is_some(),
862 "{query} was not absorbed"
863 );
864 }
865
866 for query in [
868 "BEGIN; -- go\nINSERT INTO t VALUES (1)",
869 "COMMIT; /* go */ INSERT INTO t VALUES (1)",
870 "BEGIN;; INSERT INTO t VALUES (1)",
871 "BEGIN /* previous delimiter; -- note */; INSERT INTO t VALUES (1)",
873 "SET NAMES 'a;b'; INSERT INTO t VALUES (1)",
874 "BEGIN; /*! INSERT INTO t VALUES (1) */",
876 "BEGIN /*! INSERT INTO t VALUES (1) */",
877 "/*!40101 SET NAMES utf8mb4 */; INSERT INTO t VALUES (1)",
878 "/*!40101 SET NAMES utf8mb4 */ /*! INSERT INTO t VALUES (1) */",
879 ] {
880 assert!(
881 check(query, QueryContext::arc(), session.clone()).is_none(),
882 "{query} must not be absorbed"
883 );
884 }
885
886 for query in [
888 "BEGIN /* previous delimiter; -- note */",
889 "BEGIN -- a; b",
890 "SET NAMES 'a;b'",
891 "SET NAMES \"a;b\"",
892 "SET NAMES 'it\\'s; here'",
893 "SET NAMES 'a;b';",
894 ] {
895 assert!(
896 check(query, QueryContext::arc(), session.clone()).is_some(),
897 "{query} was not absorbed"
898 );
899 }
900 }
901
902 #[test]
903 fn test_check_skips_non_federated_keywords() {
904 let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0));
905 for query in [
906 "INSERT INTO t VALUES (1)",
907 "UPDATE t SET a = 1",
908 "DELETE FROM t",
909 "CREATE TABLE t (ts TIMESTAMP TIME INDEX)",
910 "WITH x AS (SELECT 1) SELECT * FROM x",
911 "TQL EVAL (0, 10, '5s') up",
912 ] {
913 assert!(
914 check(query, QueryContext::arc(), session.clone()).is_none(),
915 "{query} must not be absorbed"
916 );
917 }
918 }
919
920 #[test]
921 fn test_strip_leading_comments() {
922 assert_eq!(strip_leading_comments("SELECT 1"), "SELECT 1");
923 assert_eq!(strip_leading_comments(" \n\tSELECT 1"), "SELECT 1");
924 assert_eq!(
925 strip_leading_comments("/* ApplicationName=DataGrip 2026.2.5 */ COMMIT"),
926 "COMMIT"
927 );
928 assert_eq!(strip_leading_comments("/* a */ /* b */COMMIT"), "COMMIT");
929 assert_eq!(strip_leading_comments("-- a comment\nCOMMIT"), "COMMIT");
930 assert_eq!(strip_leading_comments("# a comment\nCOMMIT"), "COMMIT");
931 assert_eq!(strip_leading_comments("--x\nCOMMIT"), "--x\nCOMMIT");
933 assert_eq!(strip_leading_comments("/* unterminated"), "");
935 assert_eq!(strip_leading_comments("-- trailing"), "");
936 assert_eq!(
938 strip_leading_comments("/*!40101 SET NAMES utf8mb4 */"),
939 "/*!40101 SET NAMES utf8mb4 */"
940 );
941 assert_eq!(
942 strip_leading_comments("/* App */ /*!40101 SET NAMES utf8mb4 */"),
943 "/*!40101 SET NAMES utf8mb4 */"
944 );
945 assert_eq!(
947 strip_leading_comments("/* a */SELECT /* b */ 1"),
948 "SELECT /* b */ 1"
949 );
950 }
951
952 #[test]
955 fn test_check_comment_prefixed() {
956 let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0));
957 let prefix = "/* ApplicationName=DataGrip 2026.2.5 */ ";
958
959 for query in [
960 "SET TRANSACTION READ WRITE",
961 "SET SESSION TRANSACTION READ ONLY",
962 "SET NAMES utf8mb4",
963 "BEGIN",
964 "START TRANSACTION",
965 "COMMIT",
966 "ROLLBACK",
967 "LOCK TABLES t WRITE",
969 "UNLOCK TABLES",
970 ] {
971 let output = check(
972 &format!("{prefix}{query}"),
973 QueryContext::arc(),
974 session.clone(),
975 );
976 let OutputData::RecordBatches(batches) = output
977 .unwrap_or_else(|| panic!("{query} was not absorbed"))
978 .data
979 else {
980 unreachable!()
981 };
982 assert_eq!(batches.iter().map(|b| b.num_rows()).sum::<usize>(), 0);
983 }
984
985 for query in [
988 "/*!40101 SET NAMES utf8mb4 */",
989 "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */",
990 "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */",
991 "/*!80003 SET @OLD_x=1 */",
992 ] {
993 let output = check(query, QueryContext::arc(), session.clone());
994 let OutputData::RecordBatches(batches) = output
995 .unwrap_or_else(|| panic!("{query} was not absorbed"))
996 .data
997 else {
998 unreachable!()
999 };
1000 assert_eq!(batches.iter().map(|b| b.num_rows()).sum::<usize>(), 0);
1001 }
1002
1003 for query in [
1006 "SELECT @@GLOBAL.event_scheduler",
1007 "SHOW GLOBAL VARIABLES LIKE 'event_scheduler'",
1008 ] {
1009 let output = check(
1010 &format!("{prefix}{query}"),
1011 QueryContext::arc(),
1012 session.clone(),
1013 );
1014 let OutputData::RecordBatches(batches) = output
1015 .unwrap_or_else(|| panic!("{query} was not absorbed"))
1016 .data
1017 else {
1018 unreachable!()
1019 };
1020 assert!(!batches.schema().column_schemas().is_empty(), "{query}");
1021 }
1022 }
1023}