= ({
填充到选中行 ({selectedRowKeys.length})
- e.currentTarget.style.background = darkMode ? '#303030' : '#f5f5f5'}
- onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
- onClick={() => {
- if (cellContextMenu.record) handleCopyInsert(cellContextMenu.record);
- setCellContextMenu(prev => ({ ...prev, visible: false }));
- }}
- >
- 复制为 INSERT
-
+ {supportsCopyInsert && (
+ e.currentTarget.style.background = darkMode ? '#303030' : '#f5f5f5'}
+ onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
+ onClick={() => {
+ if (cellContextMenu.record) handleCopyInsert(cellContextMenu.record);
+ setCellContextMenu(prev => ({ ...prev, visible: false }));
+ }}
+ >
+ 复制为 INSERT
+
+ )}
= ({ tab }) => {
const [showFilter, setShowFilter] = useState(false);
const [filterConditions, setFilterConditions] = useState([]);
const duckdbSafeSelectCacheRef = useRef>({});
- const currentConnType = (connections.find(c => c.id === tab.connectionId)?.config?.type || '').toLowerCase();
- const forceReadOnly = currentConnType === 'tdengine' || currentConnType === 'clickhouse';
+ const currentConnConfig = connections.find(c => c.id === tab.connectionId)?.config;
+ const currentConnCaps = getDataSourceCapabilities(currentConnConfig);
+ const currentConnType = currentConnCaps.type;
+ const forceReadOnly = currentConnCaps.forceReadOnlyQueryResult;
useEffect(() => {
setPkColumns([]);
@@ -673,6 +676,7 @@ const DataViewer: React.FC<{ tab: TabData }> = ({ tab }) => {
columnNames={columnNames}
loading={loading}
tableName={tab.tableName}
+ exportScope="table"
dbName={tab.dbName}
connectionId={tab.connectionId}
pkColumns={pkColumns}
diff --git a/frontend/src/components/QueryEditor.tsx b/frontend/src/components/QueryEditor.tsx
index 347f43e..2d6a36e 100644
--- a/frontend/src/components/QueryEditor.tsx
+++ b/frontend/src/components/QueryEditor.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useRef } from 'react';
+import React, { useState, useEffect, useRef, useMemo } from 'react';
import Editor, { OnMount } from '@monaco-editor/react';
import { Button, message, Modal, Input, Form, Dropdown, MenuProps, Tooltip, Select, Tabs } from 'antd';
import { PlayCircleOutlined, SaveOutlined, FormatPainterOutlined, SettingOutlined, CloseOutlined } from '@ant-design/icons';
@@ -7,6 +7,7 @@ import { TabData, ColumnDefinition } from '../types';
import { useStore } from '../store';
import { DBQuery, DBGetTables, DBGetAllColumns, DBGetDatabases, DBGetColumns } from '../../wailsjs/go/app/App';
import DataGrid, { GONAVI_ROW_KEY } from './DataGrid';
+import { getDataSourceCapabilities } from '../utils/dataSourceCapabilities';
const QueryEditor: React.FC<{ tab: TabData }> = ({ tab }) => {
const [query, setQuery] = useState(tab.query || 'SELECT * FROM ');
@@ -14,6 +15,7 @@ const QueryEditor: React.FC<{ tab: TabData }> = ({ tab }) => {
type ResultSet = {
key: string;
sql: string;
+ exportSql?: string;
rows: any[];
columns: string[];
tableName?: string;
@@ -47,6 +49,10 @@ const QueryEditor: React.FC<{ tab: TabData }> = ({ tab }) => {
const visibleDbsRef = useRef([]); // Store visible databases for cross-db intellisense
const connections = useStore(state => state.connections);
+ const queryCapableConnections = useMemo(
+ () => connections.filter(c => getDataSourceCapabilities(c.config).supportsQueryEditor),
+ [connections]
+ );
const addSqlLog = useStore(state => state.addSqlLog);
const currentConnectionIdRef = useRef(currentConnectionId);
const currentDbRef = useRef(currentDb);
@@ -64,6 +70,16 @@ const QueryEditor: React.FC<{ tab: TabData }> = ({ tab }) => {
currentConnectionIdRef.current = currentConnectionId;
}, [currentConnectionId]);
+ useEffect(() => {
+ if (!queryCapableConnections.some(c => c.id === currentConnectionId)) {
+ const fallback = queryCapableConnections[0]?.id || '';
+ if (fallback && fallback !== currentConnectionId) {
+ setCurrentConnectionId(fallback);
+ setCurrentDb('');
+ }
+ }
+ }, [queryCapableConnections, currentConnectionId]);
+
useEffect(() => {
currentDbRef.current = currentDb;
}, [currentDb]);
@@ -977,6 +993,12 @@ const QueryEditor: React.FC<{ tab: TabData }> = ({ tab }) => {
if (runSeqRef.current === runSeq) setLoading(false);
return;
}
+ const connCaps = getDataSourceCapabilities(conn.config);
+ if (!connCaps.supportsQueryEditor) {
+ message.error("当前数据源不支持 SQL 查询编辑器,请使用对应专用页面。");
+ if (runSeqRef.current === runSeq) setLoading(false);
+ return;
+ }
const config = {
...conn.config,
@@ -1000,8 +1022,7 @@ const QueryEditor: React.FC<{ tab: TabData }> = ({ tab }) => {
const nextResultSets: ResultSet[] = [];
const maxRows = Number(queryOptions?.maxRows) || 0;
const dbType = String((config as any).type || 'mysql');
- const normalizedDbType = dbType.toLowerCase();
- const forceReadOnlyResult = normalizedDbType === 'tdengine' || normalizedDbType === 'clickhouse';
+ const forceReadOnlyResult = connCaps.forceReadOnlyQueryResult;
const wantsLimitProbe = Number.isFinite(maxRows) && maxRows > 0;
const probeLimit = wantsLimitProbe ? (maxRows + 1) : 0;
let anyTruncated = false;
@@ -1066,6 +1087,7 @@ const QueryEditor: React.FC<{ tab: TabData }> = ({ tab }) => {
nextResultSets.push({
key: `result-${idx + 1}`,
sql: rawStatement,
+ exportSql: limited.applied ? applyAutoLimit(rawStatement, dbType, Math.max(1, Number(maxRows) || 1)).sql : rawStatement,
rows,
columns: cols,
tableName: simpleTableName,
@@ -1082,6 +1104,7 @@ const QueryEditor: React.FC<{ tab: TabData }> = ({ tab }) => {
nextResultSets.push({
key: `result-${idx + 1}`,
sql: rawStatement,
+ exportSql: rawStatement,
rows: [row],
columns: ['affectedRows'],
pkColumns: [],
@@ -1223,7 +1246,7 @@ const QueryEditor: React.FC<{ tab: TabData }> = ({ tab }) => {
setCurrentConnectionId(val);
setCurrentDb('');
}}
- options={connections.map(c => ({ label: c.name, value: c.id }))}
+ options={queryCapableConnections.map(c => ({ label: c.name, value: c.id }))}
showSearch
/>