fix(mysql): allow connection-level queries without database

This commit is contained in:
Freedom
2026-07-16 15:38:06 +08:00
committed by GitHub
parent 79c682679d
commit eb4abba713
5 changed files with 59 additions and 10 deletions
@@ -66,6 +66,14 @@ describe("requiresDatabaseSelection", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), "CREATE DATABASE app_db")).toBe(false);
});
it("allows MySQL SHOW DATABASES to run without a selected database", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), "SHOW DATABASES")).toBe(false);
});
it("allows MySQL SHOW VARIABLES without a selected database", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), "SHOW VARIABLES LIKE 'version%'")).toBe(false);
});
it("allows MySQL CREATE SCHEMA with options to run without a selected database", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), "CREATE SCHEMA `app-db` DEFAULT CHARACTER SET utf8mb4")).toBe(false);
});
@@ -78,16 +86,20 @@ describe("requiresDatabaseSelection", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), "SET NAMES utf8mb4; DROP DATABASE IF EXISTS app_db; CREATE DATABASE app_db; USE app_db; INSERT INTO users VALUES (1)")).toBe(false);
});
it("requires a database when MySQL batch statements never establish database context", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), "CREATE DATABASE app_db; CREATE TABLE users(id INT)")).toBe(true);
it("lets MySQL report statement-specific database requirements", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), "CREATE DATABASE app_db; CREATE TABLE users(id INT)")).toBe(false);
});
it("requires a database when a USE statement is not a standalone database switch", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), "CREATE DATABASE app_db; USE app_db SELECT 1; CREATE TABLE users(id INT)")).toBe(true);
it("lets MySQL reject malformed database switches", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), "CREATE DATABASE app_db; USE app_db SELECT 1; CREATE TABLE users(id INT)")).toBe(false);
});
it("still requires a database for ordinary MySQL queries", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), "SELECT * FROM users")).toBe(true);
it.each(["SELECT 1", "SELECT VERSION()", "SELECT * FROM mysql.user", "SELECT * FROM users"])("allows connection-level MySQL query: %s", (sql) => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), sql)).toBe(false);
});
it("still requires a database for non-MySQL multi-database connections", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mssql"), "SELECT * FROM dbo.users")).toBe(true);
});
it("allows HANA with default database (empty string) to execute queries", () => {
@@ -6,7 +6,7 @@ import { useConnectionStore } from "@/stores/connectionStore";
import { useSettingsStore } from "@/stores/settingsStore";
import { useToast } from "@/composables/useToast";
import { isSingleDatabase, usesTreeSchemaMode } from "@/lib/database/databaseCapabilities";
import { canExecuteWithoutSelectedDatabase } from "@/lib/connection/connectionLevelDatabaseBootstrap";
import { supportsConnectionLevelSqlExecution } from "@/lib/connection/connectionLevelDatabaseBootstrap";
import { classifySqlActivityKind } from "@/lib/history/historyActivityKind";
import { sqlMetadataRefreshTarget } from "@/lib/sql/sqlMetadataRefresh";
import { isMysqlExecutionErrorResult, usesMysqlProtocolDatabaseType } from "@/lib/query/queryResultError";
@@ -294,12 +294,14 @@ function supportsSqlTemplateParameters(connection: ConnectionConfig | undefined)
return connection.db_type !== "redis" && connection.db_type !== "mongodb";
}
export function requiresDatabaseSelection(tab: QueryTab, connection: ConnectionConfig | undefined, sql = ""): boolean {
export function requiresDatabaseSelection(tab: QueryTab, connection: ConnectionConfig | undefined, _sql = ""): boolean {
if (tab.mode !== "query") return false;
if (!connection) return false;
if (tab.database) return false;
if (tab.database === "" && usesTreeSchemaMode(connection.db_type)) return false;
if (isSingleDatabase(connection.db_type)) return false;
if (canExecuteWithoutSelectedDatabase(connection, sql)) return false;
// MySQL-compatible servers decide per statement whether a default database is required.
// Keep interactive execution connection-scoped instead of rejecting valid qualified or constant queries.
if (supportsConnectionLevelSqlExecution(connection)) return false;
return !["elasticsearch", "qdrant", "milvus", "weaviate", "chromadb", "zookeeper"].includes(connection.db_type);
}
@@ -167,6 +167,13 @@ function isAllowedBootstrapPrelude(statement: string): boolean {
return true;
}
// Connection-scoped SHOW (DATABASES, VARIABLES, PROCESSLIST, …) does not need a
// selected schema. SHOW TABLES / SHOW COLUMNS still fail at the server when no
// database is selected — matching Navicat-style query windows.
if (normalized === "SHOW") {
return true;
}
if (normalized !== "CREATE" && normalized !== "DROP") {
return false;
}
@@ -175,12 +182,16 @@ function isAllowedBootstrapPrelude(statement: string): boolean {
return !!next && isDatabaseKeyword(next[0]);
}
export function supportsConnectionLevelDatabaseBootstrap(connection: BootstrapConnection | undefined): boolean {
export function supportsConnectionLevelSqlExecution(connection: BootstrapConnection | undefined): boolean {
if (!connection) return false;
if (supportsCreateDatabaseCharset(connection.db_type, connection.driver_profile)) return true;
return !!connection.driver_profile && MYSQL_BOOTSTRAP_EXTRA_PROFILES.has(connection.driver_profile.toLowerCase());
}
export function supportsConnectionLevelDatabaseBootstrap(connection: BootstrapConnection | undefined): boolean {
return supportsConnectionLevelSqlExecution(connection);
}
export function canExecuteWithoutSelectedDatabase(connection: BootstrapConnection | undefined, sql: string): boolean {
if (!supportsConnectionLevelDatabaseBootstrap(connection)) return false;
+13
View File
@@ -695,6 +695,12 @@ pub fn mysql_like_sql_file_can_execute_without_selected_database(file_content: &
continue;
}
// Connection-scoped SHOW (DATABASES, VARIABLES, PROCESSLIST, …) does not
// need a selected schema. Object-scoped SHOW still fails at the server.
if keyword.eq_ignore_ascii_case("SHOW") {
continue;
}
if mysql_use_database_target(statement).is_some() {
has_database_context = true;
continue;
@@ -1431,9 +1437,16 @@ mod tests {
assert!(mysql_like_sql_file_can_execute_without_selected_database(
"SET NAMES utf8mb4;\nCREATE DATABASE app_db;\n-- switch tenant\nUSE app_db;\nCREATE TABLE users(id INT)"
));
assert!(mysql_like_sql_file_can_execute_without_selected_database("SHOW DATABASES"));
assert!(mysql_like_sql_file_can_execute_without_selected_database(
"SHOW SCHEMAS;\nSHOW VARIABLES LIKE 'version%'"
));
assert!(!mysql_like_sql_file_can_execute_without_selected_database(
"CREATE DATABASE app_db;\nCREATE TABLE users(id INT)"
));
assert!(!mysql_like_sql_file_can_execute_without_selected_database(
"SHOW DATABASES;\nCREATE TABLE users(id INT)"
));
assert!(!mysql_like_sql_file_can_execute_without_selected_database(
"CREATE DATABASE app_db;\nUSE app_db SELECT 1;\nCREATE TABLE users(id INT)"
));
@@ -32,11 +32,22 @@ test("allows install scripts that create and switch databases before table DDL",
assert.equal(canExecuteWithoutSelectedDatabase(connection("mysql"), sql), true);
});
test("allows connection-scoped SHOW without a selected database", () => {
assert.equal(canExecuteWithoutSelectedDatabase(connection("mysql"), "SHOW DATABASES"), true);
assert.equal(canExecuteWithoutSelectedDatabase(connection("mysql"), "SHOW SCHEMAS; SHOW VARIABLES LIKE 'version%'"), true);
assert.equal(canExecuteWithoutSelectedDatabase(connection("doris"), "show databases"), true);
assert.equal(canExecuteWithoutSelectedDatabase(connection("goldendb"), "SHOW PROCESSLIST"), true);
});
test("blocks scripts that never establish database context", () => {
const sql = "CREATE DATABASE app_db; CREATE TABLE users(id INT)";
assert.equal(canExecuteWithoutSelectedDatabase(connection("mysql"), sql), false);
});
test("blocks SHOW followed by table DDL without a database context", () => {
assert.equal(canExecuteWithoutSelectedDatabase(connection("mysql"), "SHOW DATABASES; CREATE TABLE users(id INT)"), false);
});
test("blocks malformed USE statements that trail into other SQL", () => {
const sql = "CREATE DATABASE app_db; USE app_db SELECT 1; CREATE TABLE users(id INT)";
assert.equal(canExecuteWithoutSelectedDatabase(connection("mysql"), sql), false);