mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-09-21 12:45:37 +00:00
fix(mysql): strip leading comments before federated statement filter (#9156)
Backport upstream 7cbd20a053 for DataGrip/JDBC introspection compatibility.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -383,6 +383,47 @@ pub(super) fn get_schema_columns(table_name: &str) -> (SchemaRef, Vec<VectorRef>
|
||||
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),
|
||||
};
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 -----
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<DataType> {
|
||||
Ok(DataType::Utf8View)
|
||||
}
|
||||
fn return_type(&self, _: &[DataType]) -> datafusion_common::Result<DataType> {
|
||||
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<ColumnarValue> {
|
||||
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<ColumnarValue> {
|
||||
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<DataType> {
|
||||
Ok(DataType::Utf8View)
|
||||
}
|
||||
|
||||
fn signature(&self) -> &Signature {
|
||||
&self.signature
|
||||
}
|
||||
|
||||
fn invoke_with_args(
|
||||
&self,
|
||||
args: ScalarFunctionArgs,
|
||||
) -> datafusion_common::Result<ColumnarValue> {
|
||||
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
|
||||
|
||||
@@ -31,23 +31,47 @@ use regex::bytes::RegexSet;
|
||||
use session::SessionRef;
|
||||
use session::context::QueryContextRef;
|
||||
|
||||
static SELECT_VAR_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new("(?i)^(SELECT @@(.*))").unwrap());
|
||||
static MYSQL_CONN_JAVA_PATTERN: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new("(?i)^(/\\* mysql-connector-j(.*))").unwrap());
|
||||
static SHOW_LOWER_CASE_PATTERN: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new("(?i)^(SHOW VARIABLES LIKE 'lower_case_table_names'(.*))").unwrap());
|
||||
static SHOW_VARIABLES_LIKE_PATTERN: Lazy<Regex> =
|
||||
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<Regex> =
|
||||
Lazy::new(|| Regex::new("(?i)^(SELECT\\s+@@(.*))").unwrap());
|
||||
static SHOW_LOWER_CASE_PATTERN: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(&format!(
|
||||
"(?i)^(SHOW {VARIABLES_SCOPE}VARIABLES LIKE 'lower_case_table_names'(.*))"
|
||||
))
|
||||
.unwrap()
|
||||
});
|
||||
static SHOW_VARIABLES_LIKE_PATTERN: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(&format!(
|
||||
"(?i)^(SHOW {VARIABLES_SCOPE}VARIABLES( LIKE (.*))?)"
|
||||
))
|
||||
.unwrap()
|
||||
});
|
||||
static SHOW_WARNINGS_PATTERN: Lazy<Regex> =
|
||||
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<Regex> = 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<Regex> =
|
||||
Lazy::new(|| Regex::new("(?i)^(SELECT TIMEDIFF\\(NOW\\(\\), UTC_TIMESTAMP\\(\\)\\))").unwrap());
|
||||
|
||||
// sqlalchemy < 1.4.30
|
||||
static SHOW_SQL_MODE_PATTERN: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new("(?i)^(SHOW VARIABLES LIKE 'sql_mode'(.*))").unwrap());
|
||||
static SHOW_SQL_MODE_PATTERN: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(&format!(
|
||||
"(?i)^(SHOW {VARIABLES_SCOPE}VARIABLES LIKE 'sql_mode'(.*))"
|
||||
))
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
static OTHER_NOT_SUPPORTED_STMT: Lazy<RegexSet> = Lazy::new(|| {
|
||||
RegexSet::new([
|
||||
@@ -55,6 +79,7 @@ static OTHER_NOT_SUPPORTED_STMT: Lazy<RegexSet> = Lazy::new(|| {
|
||||
"(?i)^(ROLLBACK(.*))",
|
||||
"(?i)^(COMMIT(.*))",
|
||||
"(?i)^(START(.*))",
|
||||
"(?i)^(BEGIN(.*))",
|
||||
|
||||
// Set.
|
||||
"(?i)^(SET NAMES(.*))",
|
||||
@@ -88,13 +113,9 @@ static OTHER_NOT_SUPPORTED_STMT: Lazy<RegexSet> = 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<HashMap<&str, &str>> = 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<Output
|
||||
}
|
||||
|
||||
fn check_select_variable(query: &str, query_context: QueryContextRef) -> Option<Output> {
|
||||
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<Output> {
|
||||
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<Output> {
|
||||
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<Output> {
|
||||
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<Output> {
|
||||
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<Output> {
|
||||
// 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::<usize>(), 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::<usize>(), 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Statement> {
|
||||
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());
|
||||
|
||||
@@ -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|||
|
||||
+++++++++++++++++++
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 | | |
|
||||
|
||||
@@ -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|
|
||||
+++++++++++++++++++++++++
|
||||
|
||||
|
||||
Reference in New Issue
Block a user