Files
greptimedb/tests-integration/tests/mysql.rs
T
LFCanddennis zhuang de0664795f fix: match system schema names case-insensitively [Backport release/v1.2] (#9041)
fix: match system schema names case-insensitively (#9040)

* fix: match system schema names case-insensitively

Database names that arrive over a protocol (the MySQL handshake and
COM_INIT_DB, the Postgres startup parameter, the HTTP `db` parameter, the
gRPC dbname header) never reach the SQL parser, which is what lowercases
unquoted identifiers. Since #8062 stopped lowercasing them wholesale,
connecting to `INFORMATION_SCHEMA` in any spelling but the canonical one
fails with "Unknown database" -- including the `USE <db>` that a MySQL
client turns into COM_INIT_DB.

Fold only system schema names to their canonical spelling, so user schema
names keep the case they were created with. `is_reserved_schema_name` uses
the same match, otherwise a quoted `CREATE DATABASE "INFORMATION_SCHEMA"`
creates a schema shadowed by the system one.



* refactor: hoist system schema names into a const



---------


(cherry picked from commit fa794fae7a)

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
Co-authored-by: dennis zhuang <killme2008@gmail.com>
2026-09-07 21:01:30 +08:00

151 lines
5.0 KiB
Rust

// Copyright 2023 Greptime Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use itertools::Itertools;
use sqlx::mysql::{MySqlQueryResult, MySqlRow};
use sqlx::{Connection, MySqlConnection, Row};
use tests_integration::test_util::{StorageType, setup_mysql_server};
use tokio_stream::StreamExt;
#[tokio::test(flavor = "multi_thread")]
async fn test_mysql_multiple_statement_execution_support() -> sqlx::Result<()> {
let (mut guard, server) = setup_mysql_server(
StorageType::File,
"test_mysql_multiple_statement_execution_support",
)
.await;
let addr = server.bind_addr().unwrap();
let mut conn = MySqlConnection::connect(&format!("mysql://{addr}/public")).await?;
let query = "create table foo (ts timestamp time index, i int)";
let result = sqlx::raw_sql(query).execute(&mut conn).await?;
assert_eq!(result.rows_affected(), 0);
fn to_string(result: either::Either<MySqlQueryResult, MySqlRow>) -> String {
match result {
either::Left(result) => {
format!("OK packet (rows affected: {})", result.rows_affected())
}
either::Right(result) => {
format!(
"Row: [{}]",
(0..result.columns().len())
.map(|i| {
let i: i64 = result.get(i);
i.to_string()
})
.join(", ")
)
}
}
}
let query = "insert into foo values (1, 1); select i from foo";
let results = sqlx::raw_sql(query)
.fetch_many(&mut conn)
.collect::<sqlx::Result<Vec<_>>>()
.await?
.into_iter()
.map(to_string)
.join("\n");
let expected = r#"
OK packet (rows affected: 1)
Row: [1]
OK packet (rows affected: 0)
"#;
assert_eq!(results, expected.trim());
let query = "insert into foo values (2, 2); insert into foo values (3, 3)";
let results = sqlx::raw_sql(query)
.fetch_many(&mut conn)
.collect::<sqlx::Result<Vec<_>>>()
.await?
.into_iter()
.map(to_string)
.join("\n");
let expected = r#"
OK packet (rows affected: 1)
OK packet (rows affected: 1)
"#;
assert_eq!(results, expected.trim());
let query = "select i from foo order by i; select sum(i) from foo";
let results = sqlx::raw_sql(query)
.fetch_many(&mut conn)
.collect::<sqlx::Result<Vec<_>>>()
.await?
.into_iter()
.map(to_string)
.join("\n");
let expected = r#"
Row: [1]
Row: [2]
Row: [3]
OK packet (rows affected: 0)
Row: [6]
OK packet (rows affected: 0)
"#;
assert_eq!(results, expected.trim());
let query = "select i from foo; select i from bar";
let result = sqlx::raw_sql(query)
.fetch_many(&mut conn)
.collect::<sqlx::Result<Vec<_>>>()
.await
.unwrap_err()
.to_string();
let expected = r#"error returned from database: 1146 (42S02): (TableNotFound): Failed to plan SQL: Table not found: greptime.public.bar"#;
assert_eq!(result, expected);
let query = "select i from bar; select i from foo";
let result = sqlx::raw_sql(query)
.fetch_many(&mut conn)
.collect::<sqlx::Result<Vec<_>>>()
.await
.unwrap_err()
.to_string();
let expected = r#"error returned from database: 1146 (42S02): (TableNotFound): Failed to plan SQL: Table not found: greptime.public.bar"#;
assert_eq!(result, expected);
let _ = server.shutdown().await;
guard.remove_all().await;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_mysql_connect_system_schema_ignores_case() -> sqlx::Result<()> {
let (mut guard, server) = setup_mysql_server(
StorageType::File,
"test_mysql_connect_system_schema_ignores_case",
)
.await;
let addr = server.bind_addr().unwrap();
// The handshake database goes through the same `on_init` as `COM_INIT_DB`, which is
// what a MySQL client turns `USE <db>` into. Neither reaches the SQL parser.
let mut conn = MySqlConnection::connect(&format!("mysql://{addr}/INFORMATION_SCHEMA")).await?;
// Unqualified: resolves only if the session really switched to the system schema.
let schema: String =
sqlx::query("select table_schema from tables where table_name = 'columns'")
.fetch_one(&mut conn)
.await?
.get(0);
assert_eq!(schema, "information_schema");
let _ = server.shutdown().await;
guard.remove_all().await;
Ok(())
}