mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-09-09 06:52:19 +00:00
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>
151 lines
5.0 KiB
Rust
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(())
|
|
}
|