mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-26 08:19:44 +08:00
✨ feat(trino): 新增 Trino 可选驱动接入并补齐查询支持
- 后端新增 Trino 数据库实现与 optional driver-agent provider - 前端补齐 catalog.schema 连接配置、URI 解析与能力开关 - SQL 编辑器对 Trino 禁用托管事务并补充前后端测试
This commit is contained in:
@@ -24,7 +24,7 @@ import {
|
||||
import { extractQueryResultTableRef, type QueryResultTableRef } from '../utils/queryResultTable';
|
||||
import { quoteIdentPart, quoteQualifiedIdent } from '../utils/sql';
|
||||
import { formatSqlExecutionError } from '../utils/sqlErrorSemantics';
|
||||
import { shouldUseSqlEditorManagedTransaction } from '../utils/sqlEditorTransaction';
|
||||
import { shouldUseSqlEditorManagedTransactionForType } from '../utils/sqlEditorTransaction';
|
||||
import { findSqlStatementRanges, resolveCurrentSqlStatementRange, resolveExecutableSql } from '../utils/sqlStatementSelection';
|
||||
import { isMacLikePlatform } from '../utils/appearance';
|
||||
import { splitSidebarQualifiedName } from '../utils/sidebarLocate';
|
||||
@@ -3205,13 +3205,13 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
setActiveResultKey('');
|
||||
return;
|
||||
}
|
||||
const useManagedTransaction = shouldUseSqlEditorManagedTransaction(sourceStatements);
|
||||
const useManagedTransaction = shouldUseSqlEditorManagedTransactionForType(connCaps.type, sourceStatements);
|
||||
if (useManagedTransaction && pendingSqlTransactionRef.current) {
|
||||
message.warning(translate('query_editor.transaction.message.pending_managed_transaction'));
|
||||
return;
|
||||
}
|
||||
const managedTransactionStatementCount = sourceStatements
|
||||
.filter((statement) => shouldUseSqlEditorManagedTransaction([statement]))
|
||||
.filter((statement) => shouldUseSqlEditorManagedTransactionForType(connCaps.type, [statement]))
|
||||
.length || sourceStatements.length;
|
||||
|
||||
const forceReadOnlyResult = connCaps.forceReadOnlyQueryResult;
|
||||
|
||||
@@ -1191,7 +1191,8 @@ const renderStep2 = () => {
|
||||
dbType === "highgo" ||
|
||||
dbType === "vastbase" ||
|
||||
dbType === "opengauss" ||
|
||||
dbType === "gaussdb") &&
|
||||
dbType === "gaussdb" ||
|
||||
dbType === "trino") &&
|
||||
renderConfigSectionCard({
|
||||
sectionKey: "service",
|
||||
icon: <DatabaseOutlined />,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
buildUriFromValues,
|
||||
getConnectionParamsPlaceholder,
|
||||
getUriPlaceholder,
|
||||
parseTrinoUriToValues,
|
||||
parseUriToValues,
|
||||
} from './connectionModalUri';
|
||||
|
||||
describe('connectionModalUri trino support', () => {
|
||||
it('parses catalog and schema from a Trino URI into the database field', () => {
|
||||
expect(parseTrinoUriToValues('https://alice@127.0.0.1:8443?catalog=hive&schema=default&source=GoNavi&query_timeout=30s'))
|
||||
.toMatchObject({
|
||||
host: '127.0.0.1',
|
||||
port: 8443,
|
||||
user: 'alice',
|
||||
database: 'hive.default',
|
||||
useSSL: true,
|
||||
sslMode: 'required',
|
||||
connectionParams: 'source=GoNavi&query_timeout=30s',
|
||||
});
|
||||
});
|
||||
|
||||
it('routes generic URI parsing through the Trino parser', () => {
|
||||
expect(parseUriToValues('http://alice@127.0.0.1:8080?catalog=iceberg&schema=ods', 'trino'))
|
||||
.toMatchObject({
|
||||
host: '127.0.0.1',
|
||||
port: 8080,
|
||||
user: 'alice',
|
||||
database: 'iceberg.ods',
|
||||
});
|
||||
});
|
||||
|
||||
it('builds a Trino URI with catalog and schema in query parameters', () => {
|
||||
expect(buildUriFromValues({
|
||||
type: 'trino',
|
||||
host: '127.0.0.1',
|
||||
port: 8080,
|
||||
user: 'alice',
|
||||
database: 'hive.default',
|
||||
connectionParams: 'query_timeout=45s',
|
||||
})).toBe('http://alice@127.0.0.1:8080?query_timeout=45s&catalog=hive&schema=default&source=GoNavi');
|
||||
});
|
||||
|
||||
it('keeps dedicated Trino placeholders concise', () => {
|
||||
expect(getUriPlaceholder('trino')).toBe('http://user@127.0.0.1:8080?catalog=hive&schema=default&source=GoNavi');
|
||||
expect(getConnectionParamsPlaceholder('trino', 'mysql')).toBe('session_properties=query_max_execution_time:30m&query_timeout=30s');
|
||||
});
|
||||
});
|
||||
@@ -342,9 +342,9 @@ const parseSingleHostUri = (
|
||||
if (!hostList.length) {
|
||||
return null;
|
||||
}
|
||||
const primary = parseHostPort(
|
||||
hostList[0] || `localhost:${defaultPort}`,
|
||||
defaultPort,
|
||||
const primary = parseHostPort(
|
||||
hostList[0] || `localhost:${defaultPort}`,
|
||||
defaultPort,
|
||||
);
|
||||
return {
|
||||
host: primary?.host || "localhost",
|
||||
@@ -379,8 +379,71 @@ export const parseClickHouseHTTPUriToValues = (
|
||||
defaultPort,
|
||||
);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const skipVerify = normalizeUriBool(parsed.params.get("skip_verify"));
|
||||
return {
|
||||
host: parsed.host,
|
||||
port: parsed.port,
|
||||
user: parsed.username,
|
||||
password: parsed.password,
|
||||
database: parsed.database || "",
|
||||
clickHouseProtocol: "http",
|
||||
useSSL: isHttps,
|
||||
sslMode: isHttps ? (skipVerify ? "skip-verify" : "required") : "disable",
|
||||
...extractSSLPathValuesFromParams(parsed.params, "clickhouse"),
|
||||
connectionParams: serializeConnectionParams(parsed.params),
|
||||
};
|
||||
};
|
||||
|
||||
const splitTrinoNamespace = (
|
||||
raw: unknown,
|
||||
): { catalog: string; schema: string } => {
|
||||
const text = String(raw || "").trim();
|
||||
if (!text) {
|
||||
return { catalog: "", schema: "" };
|
||||
}
|
||||
const [catalog, schema = ""] = text.split(".", 2);
|
||||
return {
|
||||
catalog: String(catalog || "").trim(),
|
||||
schema: String(schema || "").trim(),
|
||||
};
|
||||
};
|
||||
|
||||
const joinTrinoNamespace = (catalog: string, schema: string) => {
|
||||
const safeCatalog = String(catalog || "").trim();
|
||||
const safeSchema = String(schema || "").trim();
|
||||
if (!safeCatalog) return safeSchema;
|
||||
if (!safeSchema) return safeCatalog;
|
||||
return `${safeCatalog}.${safeSchema}`;
|
||||
};
|
||||
|
||||
export const parseTrinoUriToValues = (
|
||||
uriText: string,
|
||||
): Record<string, any> | null => {
|
||||
const trimmed = String(uriText || "").trim();
|
||||
const parsed = parseSingleHostUri(
|
||||
trimmed,
|
||||
["trino", "http", "https"],
|
||||
getDefaultPortByType("trino"),
|
||||
);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
const params = new URLSearchParams(parsed.params);
|
||||
const catalog = String(params.get("catalog") || "").trim();
|
||||
const schema = String(params.get("schema") || "").trim();
|
||||
params.delete("catalog");
|
||||
params.delete("schema");
|
||||
|
||||
const skipVerify = normalizeUriBool(
|
||||
params.get("skip_verify") || params.get("skipVerify"),
|
||||
);
|
||||
params.delete("skip_verify");
|
||||
params.delete("skipVerify");
|
||||
|
||||
const namespace =
|
||||
joinTrinoNamespace(catalog, schema) || String(parsed.database || "").trim();
|
||||
return {
|
||||
host: parsed.host,
|
||||
port: parsed.port,
|
||||
@@ -827,8 +890,8 @@ export const parseUriToValues = (
|
||||
return {
|
||||
host: primary?.host || "localhost",
|
||||
port: primary?.port || defaultPort,
|
||||
user: parsed.username,
|
||||
password: parsed.password,
|
||||
user: parsed.username,
|
||||
password: parsed.password,
|
||||
database: parsed.database || "",
|
||||
rocketmqTopology:
|
||||
topology === "cluster" || hostList.length > 1 ? "cluster" : "single",
|
||||
@@ -864,11 +927,15 @@ export const parseUriToValues = (
|
||||
parsed.params.get("skip_verify") || parsed.params.get("skipVerify"),
|
||||
);
|
||||
const timeoutValue = Number(parsed.params.get("timeout"));
|
||||
return {
|
||||
host: parsed.host,
|
||||
port: parsed.port,
|
||||
user: parsed.username,
|
||||
password: parsed.password,
|
||||
return {
|
||||
host: parsed.host,
|
||||
port: parsed.port,
|
||||
user: parsed.username,
|
||||
password: parsed.password,
|
||||
database: parsed.database || "",
|
||||
useSSL: tlsEnabled,
|
||||
sslMode: tlsEnabled ? (skipVerify ? "skip-verify" : "required") : "disable",
|
||||
...extractSSLPathValuesFromParams(parsed.params, type),
|
||||
connectionParams: serializeConnectionParams(parsed.params),
|
||||
timeout:
|
||||
Number.isFinite(timeoutValue) && timeoutValue > 0
|
||||
@@ -1070,10 +1137,13 @@ export const getUriPlaceholder = (dbType: string) => {
|
||||
if (isMySQLCompatibleType(dbType)) {
|
||||
const defaultPort = getDefaultPortByType(dbType);
|
||||
const scheme =
|
||||
dbType === "diros" ? "doris" : dbType === "starrocks" ? "starrocks" : dbType === "oceanbase" ? "oceanbase" : dbType === "goldendb" ? "goldendb" : "mysql";
|
||||
if (dbType === "oceanbase") {
|
||||
return `${scheme}://sys%40oracle001:pass@127.0.0.1:${defaultPort}?protocol=oracle`;
|
||||
}
|
||||
dbType === "diros" ? "doris" : dbType === "starrocks" ? "starrocks" : dbType === "oceanbase" ? "oceanbase" : dbType === "goldendb" ? "goldendb" : "mysql";
|
||||
if (dbType === "oceanbase") {
|
||||
return `${scheme}://sys%40oracle001:pass@127.0.0.1:${defaultPort}?protocol=oracle`;
|
||||
}
|
||||
return `${scheme}://user:pass@127.0.0.1:${defaultPort},127.0.0.2:${defaultPort}/db_name?topology=replica`;
|
||||
}
|
||||
if (isFileDatabaseType(dbType)) {
|
||||
return dbType === "duckdb"
|
||||
? "duckdb:///Users/name/demo.duckdb"
|
||||
: "sqlite:///Users/name/demo.sqlite";
|
||||
@@ -1140,10 +1210,12 @@ export const getConnectionParamsPlaceholder = (
|
||||
if (isMySQLCompatibleType(dbType)) {
|
||||
return "useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false";
|
||||
}
|
||||
switch (dbType) {
|
||||
case "postgres":
|
||||
case "kingbase":
|
||||
case "highgo":
|
||||
switch (dbType) {
|
||||
case "postgres":
|
||||
case "kingbase":
|
||||
case "highgo":
|
||||
case "vastbase":
|
||||
case "opengauss":
|
||||
case "gaussdb":
|
||||
return "application_name=GoNavi&statement_timeout=30000";
|
||||
case "oracle":
|
||||
@@ -1167,9 +1239,9 @@ export const getConnectionParamsPlaceholder = (
|
||||
case "tdengine":
|
||||
return "timezone=Asia%2FShanghai";
|
||||
case "iotdb":
|
||||
return "fetchSize=1024&timeZone=Asia%2FShanghai";
|
||||
case "rocketmq":
|
||||
return "groupId=gonavi&namespace=prod&tag=TagA&pullBatchSize=32&startOffset=latest";
|
||||
return "fetchSize=1024&timeZone=Asia%2FShanghai";
|
||||
case "rocketmq":
|
||||
return "groupId=gonavi&namespace=prod&tag=TagA&pullBatchSize=32&startOffset=latest";
|
||||
case "mqtt":
|
||||
return "topics=devices%2F%2B%2Ftelemetry,%24SYS%2F%23&clientId=gonavi-desktop&qos=1&cleanSession=true&fetchWaitMs=4000";
|
||||
case "kafka":
|
||||
@@ -1178,11 +1250,49 @@ export const buildUriFromValues = (values: any) => {
|
||||
return "defaultQueue=orders.queue&exchange=events.topic&managementPathPrefix=/rabbitmq";
|
||||
default:
|
||||
return "key=value&another=value";
|
||||
}
|
||||
};
|
||||
|
||||
export const buildUriFromValues = (values: any) => {
|
||||
const type = String(values.type || "")
|
||||
}
|
||||
};
|
||||
|
||||
export const buildUriFromValues = (values: any) => {
|
||||
const type = String(values.type || "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const defaultPort = getDefaultPortByType(type);
|
||||
const host = String(values.host || "localhost").trim();
|
||||
const port = Number(values.port || defaultPort);
|
||||
const user = String(values.user || "").trim();
|
||||
const password = String(values.password || "");
|
||||
const database = String(values.database || "").trim();
|
||||
const timeout = Number(values.timeout || 30);
|
||||
const encodedAuth = user
|
||||
? `${encodeURIComponent(user)}${password ? `:${encodeURIComponent(password)}` : ""}@`
|
||||
: "";
|
||||
|
||||
if (type === "trino") {
|
||||
const params = new URLSearchParams();
|
||||
mergeConnectionParams(params, values.connectionParams);
|
||||
|
||||
const { catalog, schema } = splitTrinoNamespace(values.database);
|
||||
if (catalog) {
|
||||
params.set("catalog", catalog);
|
||||
} else {
|
||||
params.delete("catalog");
|
||||
}
|
||||
if (schema) {
|
||||
params.set("schema", schema);
|
||||
} else {
|
||||
params.delete("schema");
|
||||
}
|
||||
if (!String(params.get("source") || "").trim()) {
|
||||
params.set("source", "GoNavi");
|
||||
}
|
||||
|
||||
if (values.useSSL) {
|
||||
const mode = String(values.sslMode || "required")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (mode === "skip-verify" || mode === "preferred") {
|
||||
params.set("skip_verify", "true");
|
||||
} else {
|
||||
params.delete("skip_verify");
|
||||
}
|
||||
|
||||
@@ -315,6 +315,7 @@ const SUPPORTED_CONNECTION_TYPES = new Set([
|
||||
"starrocks",
|
||||
"sphinx",
|
||||
"clickhouse",
|
||||
"trino",
|
||||
"postgres",
|
||||
"redis",
|
||||
"tdengine",
|
||||
@@ -346,6 +347,7 @@ const SSL_SUPPORTED_CONNECTION_TYPES = new Set([
|
||||
"sphinx",
|
||||
"dameng",
|
||||
"clickhouse",
|
||||
"trino",
|
||||
"postgres",
|
||||
"sqlserver",
|
||||
"oracle",
|
||||
@@ -382,6 +384,8 @@ const getDefaultPortByType = (type: string): number => {
|
||||
return 9306;
|
||||
case "clickhouse":
|
||||
return 9000;
|
||||
case "trino":
|
||||
return 8080;
|
||||
case "postgres":
|
||||
case "vastbase":
|
||||
case "opengauss":
|
||||
|
||||
@@ -111,6 +111,7 @@ describe('connectionModalPresentation', () => {
|
||||
'starrocks',
|
||||
'sphinx',
|
||||
'clickhouse',
|
||||
'trino',
|
||||
'postgres',
|
||||
'sqlserver',
|
||||
'sqlite',
|
||||
@@ -231,6 +232,14 @@ describe('connectionModalPresentation', () => {
|
||||
'credentials',
|
||||
'databaseScope',
|
||||
]);
|
||||
expect(resolveConnectionConfigLayout('trino').sections).toEqual([
|
||||
'identity',
|
||||
'uri',
|
||||
'target',
|
||||
'service',
|
||||
'credentials',
|
||||
'databaseScope',
|
||||
]);
|
||||
expect(resolveConnectionConfigLayout('gaussdb').sections).toEqual([
|
||||
'identity',
|
||||
'uri',
|
||||
|
||||
@@ -300,6 +300,19 @@ export const resolveConnectionConfigLayout = (
|
||||
],
|
||||
};
|
||||
}
|
||||
if (type === 'trino') {
|
||||
return {
|
||||
kind: 'generic-sql',
|
||||
sections: [
|
||||
'identity',
|
||||
'uri',
|
||||
'target',
|
||||
'service',
|
||||
'credentials',
|
||||
'databaseScope',
|
||||
],
|
||||
};
|
||||
}
|
||||
if (postgresCompatibleTypes.has(type)) {
|
||||
return {
|
||||
kind: 'postgres-compatible',
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('connectionTypeCapabilities', () => {
|
||||
expect(singleHostUriSchemesByType.postgres).toEqual(['postgresql', 'postgres']);
|
||||
expect(singleHostUriSchemesByType.opengauss).toContain('jdbc:opengauss');
|
||||
expect(singleHostUriSchemesByType.gaussdb).toEqual(['gaussdb', 'postgresql', 'postgres']);
|
||||
expect(singleHostUriSchemesByType.trino).toEqual(['trino', 'http', 'https']);
|
||||
expect(singleHostUriSchemesByType.dameng).toEqual(['dameng', 'dm']);
|
||||
expect(singleHostUriSchemesByType.elasticsearch).toEqual(['http', 'https']);
|
||||
expect(singleHostUriSchemesByType.chroma).toEqual(['http', 'https', 'chroma']);
|
||||
@@ -28,6 +29,7 @@ describe('connectionTypeCapabilities', () => {
|
||||
expect(supportsSSLForType('redis')).toBe(true);
|
||||
expect(supportsSSLForType('MongoDB')).toBe(true);
|
||||
expect(supportsSSLForType('elasticsearch')).toBe(true);
|
||||
expect(supportsSSLForType('trino')).toBe(true);
|
||||
expect(supportsSSLForType('gaussdb')).toBe(true);
|
||||
expect(supportsSSLForType('greatdb')).toBe(true);
|
||||
expect(supportsSSLForType('chroma')).toBe(true);
|
||||
@@ -45,7 +47,9 @@ describe('connectionTypeCapabilities', () => {
|
||||
expect(supportsSSLCAPathForType('gaussdb')).toBe(true);
|
||||
expect(supportsSSLClientCertificateForType('gaussdb')).toBe(true);
|
||||
expect(supportsSSLCAPathForType('sqlserver')).toBe(true);
|
||||
expect(supportsSSLCAPathForType('trino')).toBe(true);
|
||||
expect(supportsSSLClientCertificateForType('sqlserver')).toBe(false);
|
||||
expect(supportsSSLClientCertificateForType('trino')).toBe(true);
|
||||
expect(supportsSSLCAPathForType('redis')).toBe(true);
|
||||
expect(supportsSSLClientCertificateForType('redis')).toBe(true);
|
||||
expect(supportsSSLCAPathForType('chroma')).toBe(true);
|
||||
@@ -80,6 +84,7 @@ describe('connectionTypeCapabilities', () => {
|
||||
expect(supportsConnectionParamsForType('gdb')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('postgres')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('gaussdb')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('trino')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('oracle')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('mongodb')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('dameng')).toBe(true);
|
||||
|
||||
@@ -3,6 +3,7 @@ export const singleHostUriSchemesByType: Record<string, string[]> = {
|
||||
opengauss: ["opengauss", "jdbc:opengauss", "postgresql", "postgres"],
|
||||
gaussdb: ["gaussdb", "postgresql", "postgres"],
|
||||
clickhouse: ["clickhouse"],
|
||||
trino: ["trino", "http", "https"],
|
||||
oracle: ["oracle"],
|
||||
sqlserver: ["sqlserver"],
|
||||
iris: ["iris", "intersystems"],
|
||||
@@ -55,6 +56,7 @@ const sslSupportedTypes = new Set([
|
||||
"sphinx",
|
||||
"dameng",
|
||||
"clickhouse",
|
||||
"trino",
|
||||
"postgres",
|
||||
"sqlserver",
|
||||
"oracle",
|
||||
@@ -86,6 +88,7 @@ const sslCAPathSupportedTypes = new Set([
|
||||
"starrocks",
|
||||
"sphinx",
|
||||
"clickhouse",
|
||||
"trino",
|
||||
"postgres",
|
||||
"sqlserver",
|
||||
"kingbase",
|
||||
@@ -113,6 +116,7 @@ const sslClientCertificateSupportedTypes = new Set([
|
||||
"sphinx",
|
||||
"dameng",
|
||||
"clickhouse",
|
||||
"trino",
|
||||
"postgres",
|
||||
"kingbase",
|
||||
"highgo",
|
||||
@@ -167,6 +171,7 @@ export const supportsConnectionParamsForType = (type: string) =>
|
||||
type === "sqlserver" ||
|
||||
type === "iris" ||
|
||||
type === "clickhouse" ||
|
||||
type === "trino" ||
|
||||
type === "mongodb" ||
|
||||
type === "dameng" ||
|
||||
type === "tdengine" ||
|
||||
|
||||
@@ -24,6 +24,7 @@ describe('connectionTypeCatalog', () => {
|
||||
expect(keys).toContain('oceanbase');
|
||||
expect(keys).toContain('gaussdb');
|
||||
expect(keys).toContain('goldendb');
|
||||
expect(keys).toContain('trino');
|
||||
expect(keys).toContain('mongodb');
|
||||
expect(keys).toContain('redis');
|
||||
expect(keys).toContain('elasticsearch');
|
||||
@@ -46,6 +47,7 @@ describe('connectionTypeCatalog', () => {
|
||||
expect(getConnectionTypeDefaultPort('redis')).toBe(6379);
|
||||
expect(getConnectionTypeDefaultPort('oracle')).toBe(1521);
|
||||
expect(getConnectionTypeDefaultPort('mongodb')).toBe(27017);
|
||||
expect(getConnectionTypeDefaultPort('trino')).toBe(8080);
|
||||
expect(getConnectionTypeDefaultPort('elasticsearch')).toBe(9200);
|
||||
expect(getConnectionTypeDefaultPort('chroma')).toBe(8000);
|
||||
expect(getConnectionTypeDefaultPort('qdrant')).toBe(6333);
|
||||
@@ -66,6 +68,7 @@ describe('connectionTypeCatalog', () => {
|
||||
expect(getConnectionTypeHint('kafka')).toContain('Consumer Group');
|
||||
expect(getConnectionTypeHint('oceanbase')).toBe('MySQL / Oracle 租户');
|
||||
expect(getConnectionTypeHint('goldendb')).toBe('MySQL 兼容 / 分布式事务');
|
||||
expect(getConnectionTypeHint('trino')).toBe('HTTP / HTTPS / catalog.schema');
|
||||
expect(getConnectionTypeHint('duckdb')).toBe('本地文件连接');
|
||||
expect(getConnectionTypeHint('mysql')).toBe('标准连接配置');
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ export const CONNECTION_TYPE_GROUPS: ConnectionTypeCatalogGroup[] = [
|
||||
{ key: 'starrocks', name: 'StarRocks' },
|
||||
{ key: 'sphinx', name: 'Sphinx' },
|
||||
{ key: 'clickhouse', name: 'ClickHouse' },
|
||||
{ key: 'trino', name: 'Trino' },
|
||||
{ key: 'postgres', name: 'PostgreSQL' },
|
||||
{ key: 'sqlserver', name: 'SQL Server' },
|
||||
{ key: 'iris', name: 'InterSystems IRIS' },
|
||||
@@ -97,6 +98,8 @@ export const getConnectionTypeDefaultPort = (type: string): number => {
|
||||
return 9306;
|
||||
case 'clickhouse':
|
||||
return 9000;
|
||||
case 'trino':
|
||||
return 8080;
|
||||
case 'postgres':
|
||||
case 'opengauss':
|
||||
case 'gaussdb':
|
||||
@@ -180,6 +183,8 @@ export const getConnectionTypeHint = (type: string): string => {
|
||||
case 'sqlite':
|
||||
case 'duckdb':
|
||||
return '本地文件连接';
|
||||
case 'trino':
|
||||
return 'HTTP / HTTPS / catalog.schema';
|
||||
default:
|
||||
return '标准连接配置';
|
||||
}
|
||||
|
||||
@@ -58,6 +58,19 @@ describe('dataSourceCapabilities', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('treats Trino as an editable SQL datasource without database-level DDL shortcuts', () => {
|
||||
expect(getDataSourceCapabilities({ type: 'trino' })).toMatchObject({
|
||||
type: 'trino',
|
||||
supportsQueryEditor: true,
|
||||
supportsSqlQueryExport: true,
|
||||
supportsCopyInsert: true,
|
||||
supportsCreateDatabase: false,
|
||||
supportsRenameDatabase: false,
|
||||
supportsDropDatabase: false,
|
||||
forceReadOnlyQueryResult: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps InterSystems IRIS as an editable SQL datasource capability', () => {
|
||||
expect(getDataSourceCapabilities({ type: 'iris' })).toMatchObject({
|
||||
type: 'iris',
|
||||
|
||||
@@ -111,6 +111,7 @@ const SQL_QUERY_EXPORT_TYPES = new Set([
|
||||
'dameng',
|
||||
'tdengine',
|
||||
'clickhouse',
|
||||
'trino',
|
||||
]);
|
||||
|
||||
const COPY_INSERT_TYPES = new Set([
|
||||
@@ -135,6 +136,7 @@ const COPY_INSERT_TYPES = new Set([
|
||||
'dameng',
|
||||
'tdengine',
|
||||
'clickhouse',
|
||||
'trino',
|
||||
]);
|
||||
|
||||
const QUERY_EDITOR_DISABLED_TYPES = new Set(['redis']);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
resolveSqlEditorOperationKeyword,
|
||||
shouldUseSqlEditorManagedTransaction,
|
||||
shouldUseSqlEditorManagedTransactionForType,
|
||||
} from './sqlEditorTransaction';
|
||||
|
||||
describe('sqlEditorTransaction', () => {
|
||||
@@ -44,4 +45,10 @@ describe('sqlEditorTransaction', () => {
|
||||
'DELETE FROM users WHERE id = 1',
|
||||
])).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps Trino DML on the plain multi-statement execution path', () => {
|
||||
expect(shouldUseSqlEditorManagedTransactionForType('trino', [
|
||||
'UPDATE hive.default.orders SET status = \'done\'',
|
||||
])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -249,7 +249,13 @@ const isSqlEditorTransactionControlStatement = (statement: string): boolean => {
|
||||
return keyword === 'start' && /\btransaction\b/i.test(statement);
|
||||
};
|
||||
|
||||
export const shouldUseSqlEditorManagedTransaction = (statements: string[]): boolean => {
|
||||
export const shouldUseSqlEditorManagedTransactionForType = (
|
||||
type: string,
|
||||
statements: string[],
|
||||
): boolean => {
|
||||
if (String(type || '').trim().toLowerCase() === 'trino') {
|
||||
return false;
|
||||
}
|
||||
let hasManagedWrite = false;
|
||||
for (const statement of statements) {
|
||||
const trimmed = String(statement || '').trim();
|
||||
@@ -265,3 +271,6 @@ export const shouldUseSqlEditorManagedTransaction = (statements: string[]): bool
|
||||
}
|
||||
return hasManagedWrite;
|
||||
};
|
||||
|
||||
export const shouldUseSqlEditorManagedTransaction = (statements: string[]): boolean =>
|
||||
shouldUseSqlEditorManagedTransactionForType('', statements);
|
||||
|
||||
Reference in New Issue
Block a user