mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(mysql): allow connection-level queries without database
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user