fix(mysql): allow create database without selection

This commit is contained in:
啥都不会
2026-06-30 21:56:47 +08:00
committed by GitHub
parent b90e4fd64e
commit 427d0c548b
2 changed files with 65 additions and 3 deletions
@@ -0,0 +1,49 @@
import { describe, expect, it } from "vitest";
import { requiresDatabaseSelection } from "../useSqlExecution";
import type { ConnectionConfig, QueryTab } from "@/types/database";
function connection(dbType: ConnectionConfig["db_type"]): ConnectionConfig {
return {
id: "conn-1",
name: "Local",
db_type: dbType,
host: "localhost",
port: 3306,
username: "root",
password: "",
};
}
function queryTab(database = ""): QueryTab {
return {
id: "tab-1",
connectionId: "conn-1",
database,
schema: undefined,
title: "SQL",
sql: "",
mode: "query",
isDirty: false,
isExecuting: false,
isCancelling: false,
isExplaining: false,
};
}
describe("requiresDatabaseSelection", () => {
it("allows MySQL CREATE DATABASE to run without a selected database", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), "CREATE DATABASE app_db")).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);
});
it("requires a database when a MySQL batch includes non-creation statements", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), "CREATE DATABASE app_db; SELECT * FROM users")).toBe(true);
});
it("still requires a database for ordinary MySQL queries", () => {
expect(requiresDatabaseSelection(queryTab(), connection("mysql"), "SELECT * FROM users")).toBe(true);
});
});
@@ -14,6 +14,7 @@ import { extractSqlParameters } from "@/lib/sqlParameters";
import type { ConnectionConfig, QueryTab } from "@/types/database";
const DANGER_RE = /^\s*(DROP|DELETE|TRUNCATE|ALTER|UPDATE|MERGE|REPLACE)\b/i;
const CONNECTION_LEVEL_CREATE_DATABASE_RE = /^\s*CREATE\s+(?:DATABASE|SCHEMA)\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"[\]\w$.-]+/i;
export function stripSqlComments(sql: string): string {
return sql
@@ -72,7 +73,7 @@ export function useSqlExecution(deps: {
const tab = deps.activeTab.value;
const sql = await resolvedExecutableSql(sqlOverride);
if (!tab || !sql.trim()) return;
if (requiresDatabaseSelection(tab, deps.activeConnection.value)) {
if (requiresDatabaseSelection(tab, deps.activeConnection.value, sql)) {
deps.onMissingDatabase?.();
return;
}
@@ -118,7 +119,7 @@ export function useSqlExecution(deps: {
sql ??= await resolvedExecutableSql();
const tab = deps.activeTab.value;
if (!tab || !sql.trim()) return;
if (requiresDatabaseSelection(tab, deps.activeConnection.value)) {
if (requiresDatabaseSelection(tab, deps.activeConnection.value, sql)) {
deps.onMissingDatabase?.();
return;
}
@@ -226,9 +227,21 @@ function supportsSqlTemplateParameters(connection: ConnectionConfig | undefined)
return connection.db_type !== "redis" && connection.db_type !== "mongodb";
}
function requiresDatabaseSelection(tab: QueryTab, connection: ConnectionConfig | undefined): boolean {
function canExecuteWithoutSelectedDatabase(connection: ConnectionConfig, sql: string): boolean {
if (connection.db_type !== "mysql") return false;
const statements = stripSqlComments(sql)
.split(";")
.map((stmt) => stmt.trim())
.filter(Boolean);
// Only connection-level database creation may bypass the selected-database guard.
if (statements.length !== 1) return false;
return CONNECTION_LEVEL_CREATE_DATABASE_RE.test(statements[0]);
}
export function requiresDatabaseSelection(tab: QueryTab, connection: ConnectionConfig | undefined, sql = ""): boolean {
if (tab.mode !== "query") return false;
if (!connection || tab.database) return false;
if (isSingleDatabase(connection.db_type)) return false;
if (canExecuteWithoutSelectedDatabase(connection, sql)) return false;
return !["elasticsearch", "qdrant", "milvus", "weaviate", "chromadb", "zookeeper"].includes(connection.db_type);
}