feat(trino): 新增 Trino 可选驱动接入并补齐查询支持

- 后端新增 Trino 数据库实现与 optional driver-agent provider
- 前端补齐 catalog.schema 连接配置、URI 解析与能力开关
- SQL 编辑器对 Trino 禁用托管事务并补充前后端测试
This commit is contained in:
Syngnat
2026-06-21 13:54:42 +08:00
parent 99b75378c3
commit 8ea7ecc477
35 changed files with 1234 additions and 42 deletions

View File

@@ -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',

View File

@@ -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',

View File

@@ -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);

View File

@@ -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" ||

View File

@@ -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('标准连接配置');
});

View File

@@ -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 '标准连接配置';
}

View File

@@ -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',

View File

@@ -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']);

View File

@@ -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);
});
});

View File

@@ -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);