diff --git a/src/catalog/src/system_schema/information_schema.rs b/src/catalog/src/system_schema/information_schema.rs index d0539a062c..8c3f4bf0f5 100644 --- a/src/catalog/src/system_schema/information_schema.rs +++ b/src/catalog/src/system_schema/information_schema.rs @@ -135,6 +135,9 @@ lazy_static! { GLOBAL_STATUS, SESSION_STATUS, PARTITIONS, + PLUGINS, + USER_PRIVILEGES, + PROCESSLIST, ]; } @@ -237,6 +240,9 @@ impl SystemSchemaProviderInner for InformationSchemaProvider { TABLE_PRIVILEGES => setup_memory_table!(TABLE_PRIVILEGES), GLOBAL_STATUS => setup_memory_table!(GLOBAL_STATUS), SESSION_STATUS => setup_memory_table!(SESSION_STATUS), + PLUGINS => setup_memory_table!(PLUGINS), + USER_PRIVILEGES => setup_memory_table!(USER_PRIVILEGES), + PROCESSLIST => setup_memory_table!(PROCESSLIST), KEY_COLUMN_USAGE => Some(Arc::new(InformationSchemaKeyColumnUsage::new( self.catalog_name.clone(), self.catalog_manager.clone(), diff --git a/src/catalog/src/system_schema/information_schema/information_memory_table.rs b/src/catalog/src/system_schema/information_schema/information_memory_table.rs index 56a84a0da1..847ad06dc3 100644 --- a/src/catalog/src/system_schema/information_schema/information_memory_table.rs +++ b/src/catalog/src/system_schema/information_schema/information_memory_table.rs @@ -383,6 +383,47 @@ pub(super) fn get_schema_columns(table_name: &str) -> (SchemaRef, Vec vec![], ), + // GreptimeDB has no pluggable components. + PLUGINS => ( + string_columns(&[ + "PLUGIN_NAME", + "PLUGIN_VERSION", + "PLUGIN_STATUS", + "PLUGIN_TYPE", + "PLUGIN_TYPE_VERSION", + "PLUGIN_LIBRARY", + "PLUGIN_LIBRARY_VERSION", + "PLUGIN_AUTHOR", + "PLUGIN_DESCRIPTION", + "PLUGIN_LICENSE", + "LOAD_OPTION", + ]), + vec![], + ), + + // Privileges are not exposed through `information_schema`, same as the other + // `*_privileges` tables above. + USER_PRIVILEGES => ( + string_columns(&["GRANTEE", "TABLE_CATALOG", "PRIVILEGE_TYPE", "IS_GRANTABLE"]), + vec![], + ), + + // Sessions are reported through `information_schema.process_list` and + // `SHOW PROCESSLIST`; this table carries the MySQL column shape only. + PROCESSLIST => ( + vec![ + bigint_column("ID"), + string_column("USER"), + string_column("HOST"), + string_column("DB"), + string_column("COMMAND"), + bigint_column("TIME"), + string_column("STATE"), + string_column("INFO"), + ], + vec![], + ), + _ => unreachable!("Unknown table in information_schema: {}", table_name), }; diff --git a/src/catalog/src/system_schema/information_schema/table_names.rs b/src/catalog/src/system_schema/information_schema/table_names.rs index f161fecb54..3e9b2e2d6d 100644 --- a/src/catalog/src/system_schema/information_schema/table_names.rs +++ b/src/catalog/src/system_schema/information_schema/table_names.rs @@ -55,3 +55,8 @@ pub const SSTS_INDEX_META: &str = "ssts_index_meta"; pub const TABLE_SEMANTICS: &str = "table_semantics"; pub const STATISTICS: &str = "statistics"; pub const RECYCLE_BIN: &str = "recycle_bin"; +pub const PLUGINS: &str = "plugins"; +pub const USER_PRIVILEGES: &str = "user_privileges"; +/// MySQL's session list. GreptimeDB reports its own sessions through [`PROCESS_LIST`]; +/// this table only exists so MySQL tooling finds the name and column shape it expects. +pub const PROCESSLIST: &str = "processlist"; diff --git a/src/common/catalog/src/consts.rs b/src/common/catalog/src/consts.rs index 8e99636d6b..196f73d402 100644 --- a/src/common/catalog/src/consts.rs +++ b/src/common/catalog/src/consts.rs @@ -122,6 +122,12 @@ pub const INFORMATION_SCHEMA_STATISTICS_TABLE_ID: u32 = 43; pub const INFORMATION_SCHEMA_RECYCLE_BIN_TABLE_ID: u32 = 44; /// id for information_schema.flow_statistics pub const INFORMATION_SCHEMA_FLOW_STATISTICS_TABLE_ID: u32 = 45; +/// id for information_schema.PLUGINS +pub const INFORMATION_SCHEMA_PLUGINS_TABLE_ID: u32 = 47; +/// id for information_schema.USER_PRIVILEGES +pub const INFORMATION_SCHEMA_USER_PRIVILEGES_TABLE_ID: u32 = 48; +/// id for information_schema.PROCESSLIST (for mysql) +pub const INFORMATION_SCHEMA_PROCESSLIST_TABLE_ID: u32 = 49; // ----- End of information_schema tables ----- diff --git a/src/common/function/src/system.rs b/src/common/function/src/system.rs index 0c1e5bee36..5d3a76ac4d 100644 --- a/src/common/function/src/system.rs +++ b/src/common/function/src/system.rs @@ -21,7 +21,8 @@ mod version; use build::BuildFunction; use database::{ - ConnectionIdFunction, DatabaseFunction, PgBackendPidFunction, ReadPreferenceFunction, + ConnectionIdFunction, CurrentUserFunction, DatabaseFunction, PgBackendPidFunction, + ReadPreferenceFunction, SchemaFunction, SystemUserFunction, UserFunction, }; use pg_catalog::PGCatalogFunction; use procedure_state::ProcedureStateFunction; @@ -37,6 +38,10 @@ impl SystemFunction { registry.register_scalar(BuildFunction::default()); registry.register_scalar(VersionFunction::default()); registry.register_scalar(DatabaseFunction::default()); + registry.register_scalar(SchemaFunction::default()); + registry.register_scalar(UserFunction::default()); + registry.register_scalar(CurrentUserFunction::default()); + registry.register_scalar(SystemUserFunction::default()); registry.register_scalar(ReadPreferenceFunction::default()); registry.register_scalar(PgBackendPidFunction::default()); registry.register_scalar(ConnectionIdFunction::default()); diff --git a/src/common/function/src/system/database.rs b/src/common/function/src/system/database.rs index b20ab1f883..1884c5525f 100644 --- a/src/common/function/src/system/database.rs +++ b/src/common/function/src/system/database.rs @@ -20,39 +20,91 @@ use crate::function::{Function, find_function_context}; use crate::system::define_nullary_udf; define_nullary_udf!(DatabaseFunction); +define_nullary_udf!(SchemaFunction); +define_nullary_udf!(UserFunction); +define_nullary_udf!(CurrentUserFunction); +define_nullary_udf!(SystemUserFunction); define_nullary_udf!(ReadPreferenceFunction); define_nullary_udf!(PgBackendPidFunction); define_nullary_udf!(ConnectionIdFunction); const DATABASE_FUNCTION_NAME: &str = "database"; +const SCHEMA_FUNCTION_NAME: &str = "schema"; +const USER_FUNCTION_NAME: &str = "user"; +const CURRENT_USER_FUNCTION_NAME: &str = "current_user"; +const SYSTEM_USER_FUNCTION_NAME: &str = "system_user"; const READ_PREFERENCE_FUNCTION_NAME: &str = "read_preference"; const PG_BACKEND_PID: &str = "pg_backend_pid"; const CONNECTION_ID: &str = "connection_id"; -impl Function for DatabaseFunction { - fn name(&self) -> &str { - DATABASE_FUNCTION_NAME - } +macro_rules! impl_current_schema_function { + ($name: ident, $fn_name: expr) => { + impl Function for $name { + fn name(&self) -> &str { + $fn_name + } - fn return_type(&self, _: &[DataType]) -> datafusion_common::Result { - Ok(DataType::Utf8View) - } + fn return_type(&self, _: &[DataType]) -> datafusion_common::Result { + Ok(DataType::Utf8View) + } - fn signature(&self) -> &Signature { - &self.signature - } + fn signature(&self) -> &Signature { + &self.signature + } - fn invoke_with_args( - &self, - args: ScalarFunctionArgs, - ) -> datafusion_common::Result { - let func_ctx = find_function_context(&args)?; - let db = func_ctx.query_ctx.current_schema(); + fn invoke_with_args( + &self, + args: ScalarFunctionArgs, + ) -> datafusion_common::Result { + let func_ctx = find_function_context(&args)?; + let db = func_ctx.query_ctx.current_schema(); - Ok(ColumnarValue::Scalar(ScalarValue::Utf8View(Some(db)))) - } + Ok(ColumnarValue::Scalar(ScalarValue::Utf8View(Some(db)))) + } + } + }; } +impl_current_schema_function!(DatabaseFunction, DATABASE_FUNCTION_NAME); +// MySQL's `SCHEMA()` is a synonym for `DATABASE()`. +impl_current_schema_function!(SchemaFunction, SCHEMA_FUNCTION_NAME); + +macro_rules! impl_current_user_function { + ($name: ident, $fn_name: expr) => { + impl Function for $name { + fn name(&self) -> &str { + $fn_name + } + + fn return_type(&self, _: &[DataType]) -> datafusion_common::Result { + Ok(DataType::Utf8View) + } + + fn signature(&self) -> &Signature { + &self.signature + } + + fn invoke_with_args( + &self, + args: ScalarFunctionArgs, + ) -> datafusion_common::Result { + let func_ctx = find_function_context(&args)?; + let user = func_ctx.query_ctx.current_user(); + + Ok(ColumnarValue::Scalar(ScalarValue::Utf8View(Some( + user.username().to_string(), + )))) + } + } + }; +} + +// GreptimeDB has no notion of a user switching identity mid-session, so `USER()`, +// `CURRENT_USER()` and `SYSTEM_USER()` all report the authenticated user. +impl_current_user_function!(UserFunction, USER_FUNCTION_NAME); +impl_current_user_function!(CurrentUserFunction, CURRENT_USER_FUNCTION_NAME); +impl_current_user_function!(SystemUserFunction, SYSTEM_USER_FUNCTION_NAME); + impl Function for ReadPreferenceFunction { fn name(&self) -> &str { READ_PREFERENCE_FUNCTION_NAME diff --git a/src/servers/src/mysql/federated.rs b/src/servers/src/mysql/federated.rs index fa3aec144c..e6c4890df9 100644 --- a/src/servers/src/mysql/federated.rs +++ b/src/servers/src/mysql/federated.rs @@ -31,23 +31,47 @@ use regex::bytes::RegexSet; use session::SessionRef; use session::context::QueryContextRef; -static SELECT_VAR_PATTERN: Lazy = Lazy::new(|| Regex::new("(?i)^(SELECT @@(.*))").unwrap()); -static MYSQL_CONN_JAVA_PATTERN: Lazy = - Lazy::new(|| Regex::new("(?i)^(/\\* mysql-connector-j(.*))").unwrap()); -static SHOW_LOWER_CASE_PATTERN: Lazy = - Lazy::new(|| Regex::new("(?i)^(SHOW VARIABLES LIKE 'lower_case_table_names'(.*))").unwrap()); -static SHOW_VARIABLES_LIKE_PATTERN: Lazy = - Lazy::new(|| Regex::new("(?i)^(SHOW VARIABLES( LIKE (.*))?)").unwrap()); +/// Matches the optional `GLOBAL`/`SESSION`/`LOCAL` scope MySQL accepts before `VARIABLES`. +const VARIABLES_SCOPE: &str = "(GLOBAL |SESSION |LOCAL )?"; + +static SELECT_VAR_PATTERN: Lazy = + Lazy::new(|| Regex::new("(?i)^(SELECT\\s+@@(.*))").unwrap()); +static SHOW_LOWER_CASE_PATTERN: Lazy = Lazy::new(|| { + Regex::new(&format!( + "(?i)^(SHOW {VARIABLES_SCOPE}VARIABLES LIKE 'lower_case_table_names'(.*))" + )) + .unwrap() +}); +static SHOW_VARIABLES_LIKE_PATTERN: Lazy = Lazy::new(|| { + Regex::new(&format!( + "(?i)^(SHOW {VARIABLES_SCOPE}VARIABLES( LIKE (.*))?)" + )) + .unwrap() +}); static SHOW_WARNINGS_PATTERN: Lazy = - Lazy::new(|| Regex::new("(?i)^(/\\* ApplicationName=.*)?SHOW WARNINGS").unwrap()); + Lazy::new(|| Regex::new("(?i)^(SHOW WARNINGS)").unwrap()); + +// Capture 1: a parenless session-user keyword. Capture 2: a user variable. Both parse as +// column references, which the planner then cannot resolve. Anchored at both ends so +// `SELECT user FROM t` still reads the column. +static SELECT_USER_OR_VAR_PATTERN: Lazy = Lazy::new(|| { + Regex::new( + "(?i)^SELECT\\s+(?:(CURRENT_USER|SESSION_USER|SYSTEM_USER|USER)|(@[a-z0-9_$.]+))\\s*;?\\s*$", + ) + .unwrap() +}); // SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP()); static SELECT_TIME_DIFF_FUNC_PATTERN: Lazy = Lazy::new(|| Regex::new("(?i)^(SELECT TIMEDIFF\\(NOW\\(\\), UTC_TIMESTAMP\\(\\)\\))").unwrap()); // sqlalchemy < 1.4.30 -static SHOW_SQL_MODE_PATTERN: Lazy = - Lazy::new(|| Regex::new("(?i)^(SHOW VARIABLES LIKE 'sql_mode'(.*))").unwrap()); +static SHOW_SQL_MODE_PATTERN: Lazy = Lazy::new(|| { + Regex::new(&format!( + "(?i)^(SHOW {VARIABLES_SCOPE}VARIABLES LIKE 'sql_mode'(.*))" + )) + .unwrap() +}); static OTHER_NOT_SUPPORTED_STMT: Lazy = Lazy::new(|| { RegexSet::new([ @@ -55,6 +79,7 @@ static OTHER_NOT_SUPPORTED_STMT: Lazy = Lazy::new(|| { "(?i)^(ROLLBACK(.*))", "(?i)^(COMMIT(.*))", "(?i)^(START(.*))", + "(?i)^(BEGIN(.*))", // Set. "(?i)^(SET NAMES(.*))", @@ -88,13 +113,9 @@ static OTHER_NOT_SUPPORTED_STMT: Lazy = Lazy::new(|| { "(?i)^(/\\*!40101 SET(.*) \\*/)$", // DBeaver. - "(?i)^(/\\* ApplicationName=(.*)SHOW PLUGINS)", - "(?i)^(/\\* ApplicationName=(.*)SHOW ENGINES)", - "(?i)^(/\\* ApplicationName=(.*)SELECT @@(.*))", - "(?i)^(/\\* ApplicationName=(.*)SHOW @@(.*))", - "(?i)^(/\\* ApplicationName=(.*)SET net_write_timeout(.*))", - "(?i)^(/\\* ApplicationName=(.*)SET SQL_SELECT_LIMIT(.*))", - "(?i)^(/\\* ApplicationName=(.*)SHOW VARIABLES(.*))", + "(?i)^(SHOW PLUGINS)", + "(?i)^(SHOW ENGINES)", + "(?i)^(SHOW @@(.*))", // pt-toolkit "(?i)^(/\\*!40101 SET(.*) \\*/)$", @@ -128,7 +149,7 @@ static VAR_VALUES: Lazy> = Lazy::new(|| { // Format: // |function_name| // |value| -fn select_function(name: &str, value: &str) -> RecordBatches { +fn select_function(name: &str, value: Option<&str>) -> RecordBatches { let schema = Arc::new(Schema::new(vec![ColumnSchema::new( name, ConcreteDataType::string_datatype(), @@ -227,16 +248,30 @@ fn select_variable(query: &str, query_context: QueryContextRef) -> Option Option { - if [&SELECT_VAR_PATTERN, &MYSQL_CONN_JAVA_PATTERN] - .iter() - .any(|r| r.is_match(query)) - { + if SELECT_VAR_PATTERN.is_match(query) { select_variable(query, query_context) } else { None } } +fn check_select_user_or_var(query: &str, query_context: QueryContextRef) -> Option { + let captures = SELECT_USER_OR_VAR_PATTERN.captures(query)?; + + let recordbatches = if let Some(keyword) = captures.get(1) { + let user = query_context.current_user(); + select_function(keyword.as_str(), Some(user.username())) + } else { + // `SET @var` is accepted and discarded, so a user variable is always unset, which + // MySQL reports as NULL. + let var = captures + .get(2) + .expect("one of the two groups always matches"); + select_function(var.as_str(), None) + }; + Some(Output::new_with_record_batches(recordbatches)) +} + fn check_show_variables(query: &str) -> Option { let recordbatches = if SHOW_SQL_MODE_PATTERN.is_match(query) { Some(show_variables( @@ -303,7 +338,7 @@ fn check_others(query: &str, _query_ctx: QueryContextRef) -> Option { let recordbatches = if SELECT_TIME_DIFF_FUNC_PATTERN.is_match(query) { Some(select_function( "TIMEDIFF(NOW(), UTC_TIMESTAMP())", - "00:00:00", + Some("00:00:00"), )) } else { None @@ -311,6 +346,164 @@ fn check_others(query: &str, _query_ctx: QueryContextRef) -> Option { recordbatches.map(Output::new_with_record_batches) } +/// Strips leading whitespace and SQL comments. +/// +/// All patterns above are anchored at the start of the statement, but JDBC clients such as +/// DataGrip and DBeaver prefix every statement they send with a `/* ApplicationName=... */` +/// comment. Without stripping it first, those statements miss every pattern and reach the +/// query engine, which rejects the ones this module exists to absorb. +fn strip_leading_comments(query: &str) -> &str { + let mut rest = query.trim_start(); + loop { + // A MySQL executable comment carries the statement itself — mysqldump emits its + // initialization as `/*!40101 SET NAMES ... */`. The patterns above match those + // verbatim, so the comment must survive. + if rest.starts_with("/*!") { + return rest; + } + if let Some(tail) = rest.strip_prefix("/*") { + // An unterminated block comment leaves no statement to match against. + let Some(end) = tail.find("*/") else { + return ""; + }; + rest = tail[end + 2..].trim_start(); + } else if rest.starts_with('#') + // MySQL only treats `--` as a comment when followed by whitespace. + || (rest.starts_with("--") + && rest[2..].chars().next().is_none_or(|c| c.is_whitespace())) + { + let Some(end) = rest.find('\n') else { + return ""; + }; + rest = rest[end + 1..].trim_start(); + } else { + return rest; + } + } +} + +/// The statement keywords that only [`OTHER_NOT_SUPPORTED_STMT`] matches. `SELECT` and +/// `SHOW` are dispatched separately below. +/// +/// Keep in sync with the patterns above: a statement whose leading keyword is absent from +/// this list and from that dispatch cannot match anything, and skips every regex. +const OTHER_LEADING_KEYWORDS: [&str; 7] = [ + "SET", "COMMIT", "ROLLBACK", "START", "BEGIN", "LOCK", "UNLOCK", +]; + +/// Returns the leading run of ASCII letters, which is the statement keyword for everything +/// this module matches. +fn leading_keyword(query: &str) -> &str { + let end = query + .find(|c: char| !c.is_ascii_alphabetic()) + .unwrap_or(query.len()); + &query[..end] +} + +/// Returns the index just past the line terminator at or after `from`. +fn line_comment_end(bytes: &[u8], from: usize) -> usize { + bytes[from..] + .iter() + .position(|c| *c == b'\n') + .map_or(bytes.len(), |p| from + p + 1) +} + +/// Returns the index just past the closing `quote` of the literal starting at `start`. +fn quoted_end(bytes: &[u8], start: usize, quote: u8) -> usize { + let mut i = start + 1; + while i < bytes.len() { + match bytes[i] { + // Backquoted identifiers take no backslash escapes. + b'\\' if quote != b'`' => i += 2, + c if c == quote => { + // A doubled quote is an escaped quote, not the end of the literal. + if bytes.get(i + 1) == Some("e) { + i += 2; + } else { + return i + 1; + } + } + _ => i += 1, + } + } + bytes.len() +} + +/// Returns true if another statement follows the first statement-level `;`. +/// +/// Expects [`strip_leading_comments`] to have run, so a leading comment is never the reason +/// an executable comment is rejected below. +/// +/// Every pattern here ends in `(.*)`, so absorbing a multi-statement request would discard +/// its trailing statements without executing them — `BEGIN; INSERT INTO t VALUES (1)` would +/// report success and write nothing. Such a request must reach the query engine, which +/// executes each statement. +/// +/// Plain comments, string literals and empty statements are skipped, so a `;` inside a +/// comment or a literal does not split the request, and `BEGIN; -- done` stays a single +/// statement. +/// +/// A `/*!...*/` executable comment carries a statement, so anything but a request that +/// starts with one — mysqldump's `/*!40101 SET NAMES ... */`, which the patterns match +/// whole — also counts as a trailing statement. +fn has_trailing_statement(query: &str) -> bool { + let bytes = query.as_bytes(); + let mut i = 0; + let mut seen_semicolon = false; + let mut seen_content = false; + + // Comparisons are all against ASCII bytes, which never occur inside a multi-byte UTF-8 + // sequence, so scanning by byte cannot mistake one for a delimiter. + while i < bytes.len() { + match bytes[i] { + b'/' if bytes.get(i + 1) == Some(&b'*') => { + if bytes.get(i + 2) == Some(&b'!') && seen_content { + return true; + } + i = match bytes[i + 2..].windows(2).position(|w| w == b"*/") { + Some(p) => i + 2 + p + 2, + // An unterminated comment runs to the end of the request. + None => bytes.len(), + }; + seen_content = true; + } + b'#' => { + i = line_comment_end(bytes, i); + seen_content = true; + } + // MySQL only treats `--` as a comment when followed by whitespace. + b'-' if bytes.get(i + 1) == Some(&b'-') + && bytes.get(i + 2).is_none_or(|c| c.is_ascii_whitespace()) => + { + i = line_comment_end(bytes, i); + seen_content = true; + } + quote @ (b'\'' | b'"' | b'`') => { + if seen_semicolon { + return true; + } + seen_content = true; + i = quoted_end(bytes, i, quote); + } + b';' => { + seen_semicolon = true; + seen_content = true; + i += 1; + } + c if c.is_ascii_whitespace() => i += 1, + _ => { + if seen_semicolon { + return true; + } + seen_content = true; + i += 1; + } + } + } + + false +} + // Check whether the query is a federated or driver setup command, // and return some faked results if there are any. pub(crate) fn check( @@ -318,21 +511,37 @@ pub(crate) fn check( query_ctx: QueryContextRef, session: SessionRef, ) -> Option { - // INSERT don't need MySQL federated check. We assume the query doesn't contain - // federated or driver setup command if it starts with a 'INSERT' statement. - let the_6th_index = query.char_indices().nth(6).map(|(i, _)| i); - if let Some(index) = the_6th_index - && query[..index].eq_ignore_ascii_case("INSERT") + let query = strip_leading_comments(query); + let keyword = leading_keyword(query); + + // Dispatch on the leading keyword so ordinary queries — INSERT, UPDATE, CREATE, and the + // `SELECT`s that carry real work — run as few regexes as possible. + let absorbed = if keyword.eq_ignore_ascii_case("SELECT") { + // First to check the query is like "select @@variables". + check_select_variable(query, query_ctx.clone()) + .or_else(|| check_select_user_or_var(query, query_ctx.clone())) + .or_else(|| check_others(query, query_ctx)) + } else if keyword.eq_ignore_ascii_case("SHOW") { + check_show_variables(query) + .or_else(|| check_show_warnings(query, &session)) + .or_else(|| check_others(query, query_ctx)) + } else if query.starts_with("/*!") + || OTHER_LEADING_KEYWORDS + .iter() + .any(|k| k.eq_ignore_ascii_case(keyword)) { + check_others(query, query_ctx) + } else { + return None; + }; + + // Only a request that is about to be absorbed needs the scan, and those are short. A + // query the patterns did not match never pays for it. + if absorbed.is_some() && has_trailing_statement(query) { return None; } - // First to check the query is like "select @@variables". - check_select_variable(query, query_ctx.clone()) - .or_else(|| check_show_variables(query)) - .or_else(|| check_show_warnings(query, &session)) - // Last check - .or_else(|| check_others(query, query_ctx)) + absorbed } #[cfg(test)] @@ -494,4 +703,260 @@ mod test { ); assert!(output.is_some()); } + + #[test] + fn test_check_select_user_or_var() { + let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0)); + + fn pretty(query: &str, session: &SessionRef) -> String { + let output = check(query, QueryContext::arc(), session.clone()) + .unwrap_or_else(|| panic!("{query} was not absorbed")); + let OutputData::RecordBatches(batches) = output.data else { + unreachable!() + }; + batches.pretty_print().unwrap() + } + + // The column name keeps the spelling the client sent. + assert_eq!( + pretty("SELECT CURRENT_USER", &session), + "\ ++--------------+ +| CURRENT_USER | ++--------------+ +| greptime | ++--------------+" + ); + assert_eq!( + pretty("select session_user;", &session), + "\ ++--------------+ +| session_user | ++--------------+ +| greptime | ++--------------+" + ); + + // A user variable is always unset. + assert_eq!( + pretty("SELECT @v", &session), + "\ ++----+ +| @v | ++----+ +| | ++----+" + ); + + // Anything that is not the whole statement must reach the query engine: these are + // column references, or real queries that happen to start with the same keyword. + for query in [ + "SELECT user FROM t", + "SELECT current_user, 1", + "SELECT @v FROM t", + "SELECT @v + 1", + "SELECT userid", + "SELECT 1", + ] { + assert!( + check(query, QueryContext::arc(), session.clone()).is_none(), + "{query} must not be absorbed" + ); + } + } + + /// A multi-statement request must reach the query engine. Absorbing it would report + /// success for the whole request while executing none of it. + #[test] + fn test_check_skips_multi_statement() { + let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0)); + for query in [ + "BEGIN; INSERT INTO t VALUES (1); COMMIT", + "BEGIN;\nINSERT INTO t VALUES (1)", + "START TRANSACTION; DELETE FROM t", + "COMMIT; INSERT INTO t VALUES (1)", + "SET NAMES utf8mb4; INSERT INTO t VALUES (1)", + "SELECT @@version; INSERT INTO t VALUES (1)", + ] { + assert!( + check(query, QueryContext::arc(), session.clone()).is_none(), + "{query} must not be absorbed" + ); + } + + // A trailing semicolon, comment or empty statement is still a single statement. + for query in [ + "BEGIN;", + "COMMIT; ", + "SET NAMES utf8mb4;\n", + "BEGIN; -- done", + "COMMIT; /* done */", + "BEGIN; # done", + "BEGIN;;", + "COMMIT; ; /* done */ ;", + "BEGIN; -- done\n", + ] { + assert!( + check(query, QueryContext::arc(), session.clone()).is_some(), + "{query} was not absorbed" + ); + } + + // A statement after a trailing comment still counts. + for query in [ + "BEGIN; -- go\nINSERT INTO t VALUES (1)", + "COMMIT; /* go */ INSERT INTO t VALUES (1)", + "BEGIN;; INSERT INTO t VALUES (1)", + // The `;` inside the comment does not end the statement; the one after it does. + "BEGIN /* previous delimiter; -- note */; INSERT INTO t VALUES (1)", + "SET NAMES 'a;b'; INSERT INTO t VALUES (1)", + // An executable comment carries a statement. + "BEGIN; /*! INSERT INTO t VALUES (1) */", + "BEGIN /*! INSERT INTO t VALUES (1) */", + "/*!40101 SET NAMES utf8mb4 */; INSERT INTO t VALUES (1)", + "/*!40101 SET NAMES utf8mb4 */ /*! INSERT INTO t VALUES (1) */", + ] { + assert!( + check(query, QueryContext::arc(), session.clone()).is_none(), + "{query} must not be absorbed" + ); + } + + // A `;` inside a comment or a literal is not a statement boundary. + for query in [ + "BEGIN /* previous delimiter; -- note */", + "BEGIN -- a; b", + "SET NAMES 'a;b'", + "SET NAMES \"a;b\"", + "SET NAMES 'it\\'s; here'", + "SET NAMES 'a;b';", + ] { + assert!( + check(query, QueryContext::arc(), session.clone()).is_some(), + "{query} was not absorbed" + ); + } + } + + #[test] + fn test_check_skips_non_federated_keywords() { + let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0)); + for query in [ + "INSERT INTO t VALUES (1)", + "UPDATE t SET a = 1", + "DELETE FROM t", + "CREATE TABLE t (ts TIMESTAMP TIME INDEX)", + "WITH x AS (SELECT 1) SELECT * FROM x", + "TQL EVAL (0, 10, '5s') up", + ] { + assert!( + check(query, QueryContext::arc(), session.clone()).is_none(), + "{query} must not be absorbed" + ); + } + } + + #[test] + fn test_strip_leading_comments() { + assert_eq!(strip_leading_comments("SELECT 1"), "SELECT 1"); + assert_eq!(strip_leading_comments(" \n\tSELECT 1"), "SELECT 1"); + assert_eq!( + strip_leading_comments("/* ApplicationName=DataGrip 2026.2.5 */ COMMIT"), + "COMMIT" + ); + assert_eq!(strip_leading_comments("/* a */ /* b */COMMIT"), "COMMIT"); + assert_eq!(strip_leading_comments("-- a comment\nCOMMIT"), "COMMIT"); + assert_eq!(strip_leading_comments("# a comment\nCOMMIT"), "COMMIT"); + // `--` without trailing whitespace is not a comment. + assert_eq!(strip_leading_comments("--x\nCOMMIT"), "--x\nCOMMIT"); + // Nothing left to match against. + assert_eq!(strip_leading_comments("/* unterminated"), ""); + assert_eq!(strip_leading_comments("-- trailing"), ""); + // Executable comments carry the statement and must survive. + assert_eq!( + strip_leading_comments("/*!40101 SET NAMES utf8mb4 */"), + "/*!40101 SET NAMES utf8mb4 */" + ); + assert_eq!( + strip_leading_comments("/* App */ /*!40101 SET NAMES utf8mb4 */"), + "/*!40101 SET NAMES utf8mb4 */" + ); + // Comments inside the statement are left alone; only the prefix is stripped. + assert_eq!( + strip_leading_comments("/* a */SELECT /* b */ 1"), + "SELECT /* b */ 1" + ); + } + + /// JDBC clients prefix every statement with a comment. Those statements must still reach + /// the federated handling, and the ones MySQL answers with a result set must keep doing so. + #[test] + fn test_check_comment_prefixed() { + let session = Arc::new(Session::new(None, Channel::Mysql, Default::default(), 0)); + let prefix = "/* ApplicationName=DataGrip 2026.2.5 */ "; + + for query in [ + "SET TRANSACTION READ WRITE", + "SET SESSION TRANSACTION READ ONLY", + "SET NAMES utf8mb4", + "BEGIN", + "START TRANSACTION", + "COMMIT", + "ROLLBACK", + // Covers the rest of OTHER_LEADING_KEYWORDS. + "LOCK TABLES t WRITE", + "UNLOCK TABLES", + ] { + let output = check( + &format!("{prefix}{query}"), + QueryContext::arc(), + session.clone(), + ); + let OutputData::RecordBatches(batches) = output + .unwrap_or_else(|| panic!("{query} was not absorbed")) + .data + else { + unreachable!() + }; + assert_eq!(batches.iter().map(|b| b.num_rows()).sum::(), 0); + } + + // mysqldump initialization arrives as executable comments, which the patterns match + // verbatim; stripping them would leave an empty statement and fail the import. + for query in [ + "/*!40101 SET NAMES utf8mb4 */", + "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */", + "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */", + "/*!80003 SET @OLD_x=1 */", + ] { + let output = check(query, QueryContext::arc(), session.clone()); + let OutputData::RecordBatches(batches) = output + .unwrap_or_else(|| panic!("{query} was not absorbed")) + .data + else { + unreachable!() + }; + assert_eq!(batches.iter().map(|b| b.num_rows()).sum::(), 0); + } + + // DataGrip reads the scheduler status through these two. A column-less output is written + // as an OK packet, which the JDBC driver reports as "statement has not returned cursor". + for query in [ + "SELECT @@GLOBAL.event_scheduler", + "SHOW GLOBAL VARIABLES LIKE 'event_scheduler'", + ] { + let output = check( + &format!("{prefix}{query}"), + QueryContext::arc(), + session.clone(), + ); + let OutputData::RecordBatches(batches) = output + .unwrap_or_else(|| panic!("{query} was not absorbed")) + .data + else { + unreachable!() + }; + assert!(!batches.schema().column_schemas().is_empty(), "{query}"); + } + } } diff --git a/src/sql/src/parsers/show_parser.rs b/src/sql/src/parsers/show_parser.rs index 075c17dabe..a039dceea6 100644 --- a/src/sql/src/parsers/show_parser.rs +++ b/src/sql/src/parsers/show_parser.rs @@ -40,6 +40,7 @@ impl ParserContext<'_> { if self.consume_token("TRIGGERS") { return self.parse_show_triggers(); } + self.consume_variables_scope(); if self.consume_token("DATABASES") || self.consume_token("SCHEMAS") { self.parse_show_databases(false) } else if self.matches_keyword(Keyword::TABLES) { @@ -147,6 +148,21 @@ impl ParserContext<'_> { } } + /// Consumes the `GLOBAL`/`SESSION`/`LOCAL` scope MySQL accepts in front of `VARIABLES` and + /// `STATUS`. GreptimeDB keeps no global/session split, so the scope is accepted and ignored. + fn consume_variables_scope(&mut self) { + let scoped = matches!( + self.parser.peek_token().token, + Token::Word(w) if matches!(w.keyword, Keyword::GLOBAL | Keyword::SESSION | Keyword::LOCAL) + ) && matches!( + self.parser.peek_nth_token(1).token, + Token::Word(w) if matches!(w.keyword, Keyword::VARIABLES | Keyword::STATUS) + ); + if scoped { + let _ = self.parser.next_token(); + } + } + fn parse_show_create_database(&mut self) -> Result { let raw_database_name = self.parse_object_name() @@ -1141,6 +1157,34 @@ mod tests { )); } + #[test] + fn test_show_variables_scope() { + for sql in [ + "SHOW STATUS", + "SHOW GLOBAL STATUS", + "SHOW SESSION STATUS", + "SHOW LOCAL STATUS", + ] { + let result = ParserContext::create_with_dialect( + sql, + &GreptimeDbDialect {}, + ParseOptions::default(), + ); + assert!( + matches!(result.unwrap()[0], Statement::ShowStatus(_)), + "{sql}" + ); + } + + // The scope is only swallowed in front of VARIABLES/STATUS. + let result = ParserContext::create_with_dialect( + "SHOW GLOBAL TABLES", + &GreptimeDbDialect {}, + ParseOptions::default(), + ); + assert!(result.is_err()); + } + fn parse_show_table_status(sql: &str) -> ShowTableStatus { let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()); diff --git a/tests/cases/standalone/common/show/show_databases_tables.result b/tests/cases/standalone/common/show/show_databases_tables.result index bdfd3b79ee..1c9e3a3246 100644 --- a/tests/cases/standalone/common/show/show_databases_tables.result +++ b/tests/cases/standalone/common/show/show_databases_tables.result @@ -46,8 +46,10 @@ SHOW TABLES; | optimizer_trace | | parameters | | partitions | +| plugins | | procedure_info | | process_list | +| processlist | | profiling | | referential_constraints | | region_info | @@ -65,6 +67,7 @@ SHOW TABLES; | table_privileges | | table_semantics | | tables | +| user_privileges | | views | +---------------------------------------+ @@ -100,8 +103,10 @@ SHOW FULL TABLES; | optimizer_trace | LOCAL TEMPORARY | | parameters | LOCAL TEMPORARY | | partitions | LOCAL TEMPORARY | +| plugins | LOCAL TEMPORARY | | procedure_info | LOCAL TEMPORARY | | process_list | LOCAL TEMPORARY | +| processlist | LOCAL TEMPORARY | | profiling | LOCAL TEMPORARY | | referential_constraints | LOCAL TEMPORARY | | region_info | LOCAL TEMPORARY | @@ -119,6 +124,7 @@ SHOW FULL TABLES; | table_privileges | LOCAL TEMPORARY | | table_semantics | LOCAL TEMPORARY | | tables | LOCAL TEMPORARY | +| user_privileges | LOCAL TEMPORARY | | views | LOCAL TEMPORARY | +---------------------------------------+-----------------+ @@ -148,8 +154,10 @@ SHOW TABLE STATUS; |optimizer_trace||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| |parameters||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| |partitions||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| +|plugins||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| |procedure_info||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| |process_list||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| +|processlist||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| |profiling||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| |referential_constraints||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| |region_info||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| @@ -167,6 +175,7 @@ SHOW TABLE STATUS; |table_privileges||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| |table_semantics||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| |tables||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| +|user_privileges||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| |views||11|Fixed|0|0|0|0|0|0|0|DATETIME|DATETIME||utf8_bin|0||| +++++++++++++++++++ diff --git a/tests/cases/standalone/common/system/database.result b/tests/cases/standalone/common/system/database.result index 4a85ffadda..d49a511faa 100644 --- a/tests/cases/standalone/common/system/database.result +++ b/tests/cases/standalone/common/system/database.result @@ -10,6 +10,22 @@ select database(); | public | +------------+ +select schema(); + ++----------+ +| schema() | ++----------+ +| public | ++----------+ + +select user(), current_user(), system_user(); + ++----------+----------------+---------------+ +| user() | current_user() | system_user() | ++----------+----------------+---------------+ +| greptime | greptime | greptime | ++----------+----------------+---------------+ + use information_schema; Affected Rows: 0 @@ -22,6 +38,14 @@ select database(); | information_schema | +--------------------+ +select schema(); + ++--------------------+ +| schema() | ++--------------------+ +| information_schema | ++--------------------+ + use public; Affected Rows: 0 diff --git a/tests/cases/standalone/common/system/database.sql b/tests/cases/standalone/common/system/database.sql index fcad11ce88..6ea3447076 100644 --- a/tests/cases/standalone/common/system/database.sql +++ b/tests/cases/standalone/common/system/database.sql @@ -2,8 +2,14 @@ use public; select database(); +select schema(); + +select user(), current_user(), system_user(); + use information_schema; select database(); +select schema(); + use public; diff --git a/tests/cases/standalone/common/system/information_schema.result b/tests/cases/standalone/common/system/information_schema.result index a6c44229a2..8e3c726ec6 100644 --- a/tests/cases/standalone/common/system/information_schema.result +++ b/tests/cases/standalone/common/system/information_schema.result @@ -33,8 +33,10 @@ order by table_schema, table_name; |greptime|information_schema|optimizer_trace|LOCALTEMPORARY|17|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| |greptime|information_schema|parameters|LOCALTEMPORARY|18|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| |greptime|information_schema|partitions|LOCALTEMPORARY|28|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| +|greptime|information_schema|plugins|LOCALTEMPORARY|47|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| |greptime|information_schema|procedure_info|LOCALTEMPORARY|34|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| |greptime|information_schema|process_list|LOCALTEMPORARY|36|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| +|greptime|information_schema|processlist|LOCALTEMPORARY|49|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| |greptime|information_schema|profiling|LOCALTEMPORARY|19|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| |greptime|information_schema|referential_constraints|LOCALTEMPORARY|20|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| |greptime|information_schema|region_info|LOCALTEMPORARY|41|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| @@ -52,6 +54,7 @@ order by table_schema, table_name; |greptime|information_schema|table_privileges|LOCALTEMPORARY|23|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| |greptime|information_schema|table_semantics|LOCALTEMPORARY|42|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| |greptime|information_schema|tables|LOCALTEMPORARY|3|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| +|greptime|information_schema|user_privileges|LOCALTEMPORARY|48|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| |greptime|information_schema|views|LOCALTEMPORARY|32|0|0|0|0|0||11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| |greptime|public|numbers|LOCALTEMPORARY|2|0|0|0|0|0|test_engine|11|Fixed|0|0|0|DATETIME|DATETIME||utf8_bin|0|||Y| +++++++++++++++++++++++++ @@ -283,6 +286,17 @@ select * from information_schema.columns order by table_schema, table_name, colu | greptime | information_schema | partitions | table_schema | 2 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | | greptime | information_schema | partitions | tablespace_name | 25 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | YES | string | | | | greptime | information_schema | partitions | update_time | 20 | | | | | 0 | | | | | select,insert | | TimestampSecond | timestamp(0) | FIELD | | YES | timestamp(0) | | | +| greptime | information_schema | plugins | load_option | 11 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | plugins | plugin_author | 8 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | plugins | plugin_description | 9 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | plugins | plugin_library | 6 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | plugins | plugin_library_version | 7 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | plugins | plugin_license | 10 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | plugins | plugin_name | 1 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | plugins | plugin_status | 3 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | plugins | plugin_type | 4 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | plugins | plugin_type_version | 5 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | plugins | plugin_version | 2 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | | greptime | information_schema | procedure_info | end_time | 4 | | | | | 3 | | | | | select,insert | | TimestampMillisecond | timestamp(3) | FIELD | | YES | timestamp(3) | | | | greptime | information_schema | procedure_info | lock_keys | 6 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | YES | string | | | | greptime | information_schema | procedure_info | procedure_id | 1 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | @@ -297,6 +311,14 @@ select * from information_schema.columns order by table_schema, table_name, colu | greptime | information_schema | process_list | query | 4 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | | greptime | information_schema | process_list | schemas | 3 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | | greptime | information_schema | process_list | start_timestamp | 7 | | | | | 3 | | | | | select,insert | | TimestampMillisecond | timestamp(3) | FIELD | | NO | timestamp(3) | | | +| greptime | information_schema | processlist | command | 5 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | processlist | db | 4 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | processlist | host | 3 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | processlist | id | 1 | | | 19 | 0 | | | | | | select,insert | | Int64 | bigint | FIELD | | NO | bigint | | | +| greptime | information_schema | processlist | info | 8 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | processlist | state | 7 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | processlist | time | 6 | | | 19 | 0 | | | | | | select,insert | | Int64 | bigint | FIELD | | NO | bigint | | | +| greptime | information_schema | processlist | user | 2 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | | greptime | information_schema | profiling | block_ops_in | 9 | | | 19 | 0 | | | | | | select,insert | | Int64 | bigint | FIELD | | NO | bigint | | | | greptime | information_schema | profiling | block_ops_out | 10 | | | 19 | 0 | | | | | | select,insert | | Int64 | bigint | FIELD | | NO | bigint | | | | greptime | information_schema | profiling | context_involuntary | 8 | | | 19 | 0 | | | | | | select,insert | | Int64 | bigint | FIELD | | NO | bigint | | | @@ -519,6 +541,10 @@ select * from information_schema.columns order by table_schema, table_name, colu | greptime | information_schema | tables | temporary | 24 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | YES | string | | | | greptime | information_schema | tables | update_time | 18 | | | | | 0 | | | | | select,insert | | TimestampSecond | timestamp(0) | FIELD | | YES | timestamp(0) | | | | greptime | information_schema | tables | version | 12 | | | 20 | 0 | | | | | | select,insert | | UInt64 | bigint unsigned | FIELD | | YES | bigint unsigned | | | +| greptime | information_schema | user_privileges | grantee | 1 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | user_privileges | is_grantable | 4 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | user_privileges | privilege_type | 3 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | +| greptime | information_schema | user_privileges | table_catalog | 2 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | NO | string | | | | greptime | information_schema | views | character_set_client | 9 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | YES | string | | | | greptime | information_schema | views | check_option | 5 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | YES | string | | | | greptime | information_schema | views | collation_connection | 10 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | YES | string | | | diff --git a/tests/cases/standalone/common/view/create.result b/tests/cases/standalone/common/view/create.result index a4451bfed1..bba220b8b9 100644 --- a/tests/cases/standalone/common/view/create.result +++ b/tests/cases/standalone/common/view/create.result @@ -113,8 +113,10 @@ SELECT * FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME, TABLE_TYPE; |greptime|information_schema|optimizer_trace|LOCALTEMPORARY|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||Y| |greptime|information_schema|parameters|LOCALTEMPORARY|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||Y| |greptime|information_schema|partitions|LOCALTEMPORARY|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||Y| +|greptime|information_schema|plugins|LOCALTEMPORARY|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||Y| |greptime|information_schema|procedure_info|LOCALTEMPORARY|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||Y| |greptime|information_schema|process_list|LOCALTEMPORARY|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||Y| +|greptime|information_schema|processlist|LOCALTEMPORARY|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||Y| |greptime|information_schema|profiling|LOCALTEMPORARY|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||Y| |greptime|information_schema|referential_constraints|LOCALTEMPORARY|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||Y| |greptime|information_schema|region_info|LOCALTEMPORARY|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||Y| @@ -135,6 +137,7 @@ SELECT * FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME, TABLE_TYPE; |greptime|public|test_table|BASETABLE|ID|ID|ID|ID|ID|ID|mito|ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||N| |greptime|public|test_view|VIEW|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||N| |greptime|public|test_view2|VIEW|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||N| +|greptime|information_schema|user_privileges|LOCALTEMPORARY|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||Y| |greptime|information_schema|views|LOCALTEMPORARY|ID|ID|ID|ID|ID|ID||ID|Fixed|ID|ID|ID|DATETIME|DATETIME||utf8_bin|ID|||Y| +++++++++++++++++++++++++