mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
feat(ignite3): add Apache Ignite 3 support
This commit is contained in:
@@ -38,6 +38,7 @@ Each agent runs as a standalone process and communicates with DBX via stdin/stdo
|
||||
| bigquery | Google BigQuery | BigQuery JDBC |
|
||||
| kylin | Apache Kylin | Kylin JDBC |
|
||||
| ignite | Apache Ignite | Ignite JDBC thin (ignite-core) |
|
||||
| ignite3 | Apache Ignite 3 | Ignite 3 JDBC thin (ignite-jdbc) |
|
||||
| sundb | SunDB | SunDB JDBC |
|
||||
| tdengine | TDengine 2.4+ | taos-connector-rust native WebSocket agent |
|
||||
| yashandb | 崖山 YashanDB | YashanDB JDBC |
|
||||
|
||||
@@ -38,6 +38,7 @@ DBX 的 Agent 驱动 —— 通过 JDBC 和原生数据库驱动支持各种数
|
||||
| bigquery | Google BigQuery | BigQuery JDBC |
|
||||
| kylin | Apache Kylin | Kylin JDBC |
|
||||
| ignite | Apache Ignite | Ignite JDBC thin(ignite-core) |
|
||||
| ignite3 | Apache Ignite 3 | Ignite 3 JDBC thin(ignite-jdbc) |
|
||||
| sundb | SunDB | SunDB JDBC |
|
||||
| tdengine | TDengine 2.4+ | taos-connector-rust 原生 WebSocket agent |
|
||||
| yashandb | 崖山 YashanDB | YashanDB JDBC |
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ def legacyStandaloneProjects = ['mongodb', 'kafka'] as Set
|
||||
def pooledJdbcProjects = [
|
||||
'access', 'bigquery', 'dameng', 'databend', 'databricks', 'db2', 'exasol',
|
||||
'firebird', 'gbase8a', 'gbase8s', 'goldendb', 'h2', 'h2-legacy', 'highgo',
|
||||
'ignite', 'informix', 'iris', 'kylin', 'oceanbase-oracle', 'oscar', 'saphana',
|
||||
'ignite', 'ignite3', 'informix', 'iris', 'kylin', 'oceanbase-oracle', 'oscar', 'saphana',
|
||||
'snowflake', 'spark', 'sqlserver-legacy', 'sundb', 'teradata', 'trino', 'uxdb',
|
||||
'vertica', 'yashandb'
|
||||
] as Set
|
||||
|
||||
@@ -14,6 +14,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
public abstract class AbstractJdbcAgent extends BaseDatabaseAgent {
|
||||
private static final String GAUSSDB_COMPATIBILITY_SQL =
|
||||
@@ -457,7 +458,18 @@ public abstract class AbstractJdbcAgent extends BaseDatabaseAgent {
|
||||
protected abstract String buildJdbcUrl(ConnectParams params);
|
||||
|
||||
protected Connection openConnection(ConnectParams params) throws Exception {
|
||||
return DriverManager.getConnection(buildJdbcUrl(params), params.getUsername(), params.getPassword());
|
||||
return DriverManager.getConnection(buildJdbcUrl(params), buildConnectionProperties(params));
|
||||
}
|
||||
|
||||
protected Properties buildConnectionProperties(ConnectParams params) {
|
||||
Properties properties = new Properties();
|
||||
if (params.getUsername() != null) {
|
||||
properties.setProperty("user", params.getUsername());
|
||||
}
|
||||
if (params.getPassword() != null) {
|
||||
properties.setProperty("password", params.getPassword());
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
protected Connection openTestConnection(ConnectParams params) throws Exception {
|
||||
|
||||
@@ -165,7 +165,7 @@ public class JdbcAgentProfile {
|
||||
return schemaSwitchPrefix + " " + quote + schema.replace(quote, quote + quote) + quote;
|
||||
}
|
||||
|
||||
private static String appendUrlParams(String url, String urlParams) {
|
||||
public static String appendUrlParams(String url, String urlParams) {
|
||||
String params = trimUrlParams(urlParams);
|
||||
if (params.isEmpty()) {
|
||||
return url;
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
@@ -25,6 +26,19 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class AbstractJdbcAgentTest {
|
||||
@Test
|
||||
void buildsStandardJdbcCredentialPropertiesByDefault() {
|
||||
TestAgent agent = new TestAgent(new TrackingConnection());
|
||||
|
||||
Properties properties = agent.buildConnectionProperties(
|
||||
new ConnectParams("localhost", 0, "demo", "user", "secret", "", "", false)
|
||||
);
|
||||
|
||||
assertEquals("user", properties.getProperty("user"));
|
||||
assertEquals("secret", properties.getProperty("password"));
|
||||
assertEquals(2, properties.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ownsConnectionLifecycleAndConnectedState() {
|
||||
TrackingConnection tracking = new TrackingConnection();
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
dependencies {
|
||||
implementation 'org.apache.ignite:ignite-jdbc:3.1.0'
|
||||
}
|
||||
|
||||
tasks.named('shadowJar') {
|
||||
manifest {
|
||||
attributes('Agent-Label': 'Apache Ignite 3', 'Main-Class': 'com.dbx.agent.ignite3.Ignite3Agent')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package com.dbx.agent.ignite3;
|
||||
|
||||
import com.dbx.agent.AbstractJdbcAgent;
|
||||
import com.dbx.agent.ColumnInfo;
|
||||
import com.dbx.agent.ConnectParams;
|
||||
import com.dbx.agent.DatabaseInfo;
|
||||
import com.dbx.agent.ForeignKeyInfo;
|
||||
import com.dbx.agent.IndexInfo;
|
||||
import com.dbx.agent.JdbcAgentProfile;
|
||||
import com.dbx.agent.MultiSessionJsonRpcServer;
|
||||
import com.dbx.agent.TableInfo;
|
||||
import com.dbx.agent.TriggerInfo;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
public final class Ignite3Agent extends AbstractJdbcAgent {
|
||||
|
||||
private static final String JDBC_USERNAME_PROPERTY = "ignite.jdbc.username";
|
||||
private static final String JDBC_PASSWORD_PROPERTY = "ignite.jdbc.password";
|
||||
|
||||
private String databaseName = "";
|
||||
|
||||
@Override
|
||||
protected String driverClass() {
|
||||
return "org.apache.ignite.jdbc.IgniteJdbcDriver";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String buildJdbcUrl(ConnectParams params) {
|
||||
return buildUrl(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties buildConnectionProperties(ConnectParams params) {
|
||||
Properties properties = new Properties();
|
||||
String username = params.getUsername();
|
||||
String password = params.getPassword();
|
||||
if ((username != null && !username.isEmpty()) || (password != null && !password.isEmpty())) {
|
||||
properties.setProperty(JDBC_USERNAME_PROPERTY, username == null ? "" : username);
|
||||
properties.setProperty(JDBC_PASSWORD_PROPERTY, password == null ? "" : password);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterConnect(ConnectParams params, Connection connection) {
|
||||
// Ignite has no catalogs; name the virtual database after the connection's
|
||||
// real default schema (e.g. PUBLIC) so any database-as-schema context that
|
||||
// leaks into setSchema targets a schema that actually exists.
|
||||
try {
|
||||
String schema = connection.getSchema();
|
||||
databaseName = schema == null || schema.isBlank() ? params.getDatabase() : schema;
|
||||
} catch (Exception e) {
|
||||
databaseName = params.getDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DatabaseInfo> listDatabases() {
|
||||
// Ignite has no catalogs; expose the connected schema group as a single
|
||||
// virtual database so the object browser stays schema-oriented like H2.
|
||||
return List.of(new DatabaseInfo(databaseName.isBlank() ? "default" : databaseName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> listSchemas() {
|
||||
return unchecked(() -> {
|
||||
List<String> result = new ArrayList<>();
|
||||
try (ResultSet rs = requireConnected().getMetaData().getSchemas()) {
|
||||
while (rs.next()) {
|
||||
result.add(rs.getString("TABLE_SCHEM"));
|
||||
}
|
||||
}
|
||||
result.sort(Comparator.naturalOrder());
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TableInfo> listTables(String schema) {
|
||||
return unchecked(() -> {
|
||||
List<TableInfo> result = new ArrayList<>();
|
||||
try (ResultSet rs = requireConnected().getMetaData().getTables(null, schema, null, null)) {
|
||||
while (rs.next()) {
|
||||
result.add(new TableInfo(
|
||||
rs.getString("TABLE_NAME"),
|
||||
normalizeTableType(rs.getString("TABLE_TYPE")),
|
||||
null
|
||||
));
|
||||
}
|
||||
}
|
||||
result.sort(Comparator.comparing(TableInfo::getName));
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ColumnInfo> getColumns(String schema, String table) {
|
||||
return unchecked(() -> {
|
||||
Set<String> primaryKeys = new LinkedHashSet<>();
|
||||
try (ResultSet rs = requireConnected().getMetaData().getPrimaryKeys(null, schema, table)) {
|
||||
while (rs.next()) {
|
||||
primaryKeys.add(rs.getString("COLUMN_NAME"));
|
||||
}
|
||||
}
|
||||
|
||||
List<ColumnInfo> result = new ArrayList<>();
|
||||
try (ResultSet rs = requireConnected().getMetaData().getColumns(null, schema, table, null)) {
|
||||
while (rs.next()) {
|
||||
String colName = rs.getString("COLUMN_NAME");
|
||||
result.add(new ColumnInfo(
|
||||
colName,
|
||||
rs.getString("TYPE_NAME"),
|
||||
"YES".equals(rs.getString("IS_NULLABLE")),
|
||||
rs.getString("COLUMN_DEF"),
|
||||
primaryKeys.contains(colName),
|
||||
null,
|
||||
emptyToNull(rs.getString("REMARKS")),
|
||||
intOrNull(rs, "COLUMN_SIZE"),
|
||||
intOrNull(rs, "DECIMAL_DIGITS"),
|
||||
null
|
||||
));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IndexInfo> listIndexes(String schema, String table) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ForeignKeyInfo> listForeignKeys(String schema, String table) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TriggerInfo> listTriggers(String schema, String table) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setSchemaSQL(String schema) {
|
||||
// JdbcConnection implements Connection.setSchema, so no SQL switch is needed.
|
||||
return "";
|
||||
}
|
||||
|
||||
static String buildUrl(ConnectParams params) {
|
||||
String connectionString = params.getConnection_string();
|
||||
if (connectionString != null && !connectionString.trim().isEmpty()) {
|
||||
return connectionString.trim();
|
||||
}
|
||||
String base = "jdbc:ignite:thin://" + params.getHost() + ":" + params.getPort();
|
||||
String database = params.getDatabase();
|
||||
String url = database == null || database.isBlank() ? base : base + "/" + database;
|
||||
// Reuse the shared JDBC URL parameter mapping so advanced options
|
||||
// (timeouts, SSL, authentication) configured in the UI reach the driver.
|
||||
return JdbcAgentProfile.appendUrlParams(url, params.getUrl_params());
|
||||
}
|
||||
|
||||
private static String normalizeTableType(String type) {
|
||||
if ("BASE TABLE".equals(type)) {
|
||||
return "TABLE";
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private static Integer intOrNull(ResultSet rs, String column) throws Exception {
|
||||
Object value = rs.getObject(column);
|
||||
return value instanceof Number ? ((Number) value).intValue() : null;
|
||||
}
|
||||
|
||||
private static String emptyToNull(String value) {
|
||||
return value == null || value.isEmpty() ? null : value;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new MultiSessionJsonRpcServer(Ignite3Agent::new).run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.dbx.agent.ignite3;
|
||||
|
||||
import com.dbx.agent.DatabaseAgent;
|
||||
import com.dbx.agent.test.JdbcFakeExecutionBehaviorTest;
|
||||
|
||||
class Ignite3AgentTest extends JdbcFakeExecutionBehaviorTest {
|
||||
@Override
|
||||
protected DatabaseAgent createAgent() {
|
||||
return new Ignite3Agent();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resultSetSql() {
|
||||
return "SELECT 1";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package com.dbx.agent.ignite3;
|
||||
|
||||
import com.dbx.agent.ConnectParams;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.Driver;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.DriverPropertyInfo;
|
||||
import java.sql.SQLException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Logger;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class Ignite3AgentUrlTest {
|
||||
|
||||
@Test
|
||||
void buildUrlUsesExplicitConnectionString() {
|
||||
ConnectParams params = new ConnectParams(
|
||||
"127.0.0.1", 10800, "PUBLIC", "", "", "",
|
||||
"jdbc:ignite:thin://10.0.0.1:10800/PUBLIC?connectionTimeout=5000", false
|
||||
);
|
||||
|
||||
Assertions.assertEquals(
|
||||
"jdbc:ignite:thin://10.0.0.1:10800/PUBLIC?connectionTimeout=5000",
|
||||
Ignite3Agent.buildUrl(params)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildUrlWithoutDatabaseOmitsSchemaSegment() {
|
||||
ConnectParams params = new ConnectParams("127.0.0.1", 10800, "", "", "", "", "", false);
|
||||
|
||||
Assertions.assertEquals("jdbc:ignite:thin://127.0.0.1:10800", Ignite3Agent.buildUrl(params));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildUrlWithoutUrlParamsStaysUnchanged() {
|
||||
ConnectParams params = new ConnectParams("127.0.0.1", 10800, "PUBLIC", "", "", null, "", false);
|
||||
|
||||
Assertions.assertEquals("jdbc:ignite:thin://127.0.0.1:10800/PUBLIC", Ignite3Agent.buildUrl(params));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildUrlAppendsAdvancedUrlParamsAfterSchema() {
|
||||
ConnectParams params = new ConnectParams(
|
||||
"127.0.0.1", 10800, "PUBLIC", "", "",
|
||||
"connectionTimeout=5000&queryTimeout=30", "", false
|
||||
);
|
||||
|
||||
Assertions.assertEquals(
|
||||
"jdbc:ignite:thin://127.0.0.1:10800/PUBLIC?connectionTimeout=5000&queryTimeout=30",
|
||||
Ignite3Agent.buildUrl(params)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildUrlPassesThroughUserParamStringVerbatim() {
|
||||
ConnectParams params = new ConnectParams(
|
||||
"127.0.0.1", 10800, "PUBLIC", "", "",
|
||||
"sslEnabled=true;trustStorePath=/etc/ignite/trust.jks", "", false
|
||||
);
|
||||
|
||||
Assertions.assertEquals(
|
||||
"jdbc:ignite:thin://127.0.0.1:10800/PUBLIC?sslEnabled=true;trustStorePath=/etc/ignite/trust.jks",
|
||||
Ignite3Agent.buildUrl(params)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildUrlStripsLeadingParamSeparators() {
|
||||
ConnectParams params = new ConnectParams("127.0.0.1", 10800, "", "", "", "?sslEnabled=true", "", false);
|
||||
|
||||
Assertions.assertEquals("jdbc:ignite:thin://127.0.0.1:10800?sslEnabled=true", Ignite3Agent.buildUrl(params));
|
||||
}
|
||||
|
||||
@Test
|
||||
void usesThePinnedIgniteJdbcDependency() throws Exception {
|
||||
Path driverJar = Path.of(
|
||||
org.apache.ignite.jdbc.IgniteJdbcDriver.class.getProtectionDomain().getCodeSource().getLocation().toURI()
|
||||
);
|
||||
|
||||
Assertions.assertEquals("ignite-jdbc-3.1.0.jar", driverJar.getFileName().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void separateCredentialsRoundTripThroughJdbcPropertiesAndIgniteParser() throws Exception {
|
||||
String username = "dbx-user";
|
||||
String password = "dbx-secret";
|
||||
String urlParams = "sslEnabled=true&connectionTimeout=5000&queryTimeout=30&connectionTimeZone=UTC";
|
||||
CapturingDriver capturingDriver = new CapturingDriver();
|
||||
DriverManager.registerDriver(capturingDriver);
|
||||
|
||||
try {
|
||||
ConnectParams captureParams = new ConnectParams(
|
||||
"127.0.0.1", 10800, "PUBLIC", username, password, urlParams,
|
||||
CapturingDriver.URL, false
|
||||
);
|
||||
Properties connectionProperties = new Ignite3Agent().buildConnectionProperties(captureParams);
|
||||
try (Connection ignored = DriverManager.getConnection(CapturingDriver.URL, connectionProperties)) {
|
||||
Assertions.assertEquals(CapturingDriver.URL, capturingDriver.url);
|
||||
}
|
||||
|
||||
Assertions.assertEquals(username, capturingDriver.properties.getProperty("ignite.jdbc.username"));
|
||||
Assertions.assertEquals(password, capturingDriver.properties.getProperty("ignite.jdbc.password"));
|
||||
Assertions.assertNull(capturingDriver.properties.getProperty("user"));
|
||||
Assertions.assertNull(capturingDriver.properties.getProperty("password"));
|
||||
|
||||
ConnectParams igniteParams = new ConnectParams(
|
||||
"127.0.0.1", 10800, "PUBLIC", username, password, urlParams, "", false
|
||||
);
|
||||
String igniteUrl = Ignite3Agent.buildUrl(igniteParams);
|
||||
Assertions.assertFalse(igniteUrl.contains(username));
|
||||
Assertions.assertFalse(igniteUrl.contains(password));
|
||||
|
||||
Map<String, String> parsed = parsedProperties(
|
||||
new org.apache.ignite.jdbc.IgniteJdbcDriver(),
|
||||
igniteUrl,
|
||||
capturingDriver.properties
|
||||
);
|
||||
Assertions.assertEquals(username, parsed.get("ignite.jdbc.username"));
|
||||
Assertions.assertEquals(password, parsed.get("ignite.jdbc.password"));
|
||||
Assertions.assertEquals("true", parsed.get("ignite.jdbc.sslEnabled"));
|
||||
Assertions.assertEquals("5000", parsed.get("ignite.jdbc.connectionTimeout"));
|
||||
Assertions.assertEquals("30", parsed.get("ignite.jdbc.queryTimeout"));
|
||||
Assertions.assertEquals("UTC", parsed.get("ignite.jdbc.connectionTimeZone"));
|
||||
} finally {
|
||||
DriverManager.deregisterDriver(capturingDriver);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void blankCredentialsDoNotEnableIgniteAuthentication() throws Exception {
|
||||
CapturingDriver capturingDriver = new CapturingDriver();
|
||||
DriverManager.registerDriver(capturingDriver);
|
||||
|
||||
try {
|
||||
ConnectParams params = new ConnectParams(
|
||||
"127.0.0.1", 10800, "PUBLIC", "", "", "", CapturingDriver.URL, false
|
||||
);
|
||||
Properties connectionProperties = new Ignite3Agent().buildConnectionProperties(params);
|
||||
try (Connection ignored = DriverManager.getConnection(CapturingDriver.URL, connectionProperties)) {
|
||||
Assertions.assertTrue(capturingDriver.properties.isEmpty());
|
||||
}
|
||||
} finally {
|
||||
DriverManager.deregisterDriver(capturingDriver);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void semicolonAdvancedParamsStayVerbatimAndAreNotSplitInQueryMode() throws Exception {
|
||||
String url = Ignite3Agent.buildUrl(new ConnectParams(
|
||||
"127.0.0.1", 10800, "PUBLIC", "", "",
|
||||
"sslEnabled=true;trustStorePath=/etc/ignite/trust.jks", "", false
|
||||
));
|
||||
|
||||
Assertions.assertEquals(
|
||||
"jdbc:ignite:thin://127.0.0.1:10800/PUBLIC?sslEnabled=true;trustStorePath=/etc/ignite/trust.jks",
|
||||
url
|
||||
);
|
||||
|
||||
Map<String, String> parsed = parsedProperties(
|
||||
new org.apache.ignite.jdbc.IgniteJdbcDriver(),
|
||||
url,
|
||||
new Properties()
|
||||
);
|
||||
Assertions.assertEquals("false", parsed.get("ignite.jdbc.sslEnabled"));
|
||||
Assertions.assertNull(parsed.get("ignite.jdbc.trustStorePath"));
|
||||
}
|
||||
|
||||
private static Map<String, String> parsedProperties(
|
||||
Driver driver,
|
||||
String url,
|
||||
Properties properties
|
||||
) throws SQLException {
|
||||
Map<String, String> parsed = new LinkedHashMap<>();
|
||||
for (DriverPropertyInfo property : driver.getPropertyInfo(url, properties)) {
|
||||
parsed.put(property.name, property.value);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
private static final class CapturingDriver implements Driver {
|
||||
private static final String URL = "jdbc:dbx-ignite3-test:";
|
||||
|
||||
private String url;
|
||||
private Properties properties;
|
||||
|
||||
@Override
|
||||
public Connection connect(String url, Properties info) {
|
||||
if (!acceptsURL(url)) {
|
||||
return null;
|
||||
}
|
||||
this.url = url;
|
||||
properties = new Properties();
|
||||
properties.putAll(info);
|
||||
return (Connection) Proxy.newProxyInstance(
|
||||
Connection.class.getClassLoader(),
|
||||
new Class<?>[]{Connection.class},
|
||||
(proxy, method, args) -> {
|
||||
if ("close".equals(method.getName())) {
|
||||
return null;
|
||||
}
|
||||
if ("isClosed".equals(method.getName())) {
|
||||
return false;
|
||||
}
|
||||
throw new UnsupportedOperationException(method.getName());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsURL(String url) {
|
||||
return URL.equals(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
|
||||
return new DriverPropertyInfo[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMajorVersion() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinorVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean jdbcCompliant() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getParentLogger() {
|
||||
return Logger.getGlobal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ goldendb native-pushdown java-sql Uses information_schema with type, filter, sta
|
||||
h2 native-pushdown java-sql Uses INFORMATION_SCHEMA with type, filter, stable order, and LIMIT/OFFSET.
|
||||
hive-go shared-fallback native-go Uses HiveServer2 metadata APIs, then applies stable filtering and paging in the native agent with a SHOW TABLES fallback.
|
||||
ignite intentional-fallback java-jdbc-metadata Uses JDBC metadata from the Ignite thin driver; no portable server-side paging API, common constraints filter locally.
|
||||
ignite3 intentional-fallback java-jdbc-metadata Uses JDBC metadata from the Ignite 3 thin driver; no portable server-side paging API, common constraints filter locally.
|
||||
informix native-pushdown java-sql Uses systables/sysprocedures with type, filter, stable order, and SKIP/FIRST.
|
||||
iotdb shared-fallback native-go Uses SHOW DEVICES or SHOW TABLES DETAILS for Tree/Table metadata, then applies stable filtering and paging in the native agent.
|
||||
kingbase-go shared-fallback native-go Uses sys_catalog/information_schema for compatibility-aware metadata, then applies stable filtering and paging in the native agent.
|
||||
|
||||
|
@@ -4,7 +4,7 @@ def infrastructureModules = ['common', 'test-support']
|
||||
def driverModules = [
|
||||
'access', 'dameng', 'goldendb', 'databend', 'databricks', 'saphana',
|
||||
'teradata', 'vertica', 'firebird', 'exasol', 'oceanbase-oracle', 'gbase8a', 'gbase8s',
|
||||
'bigquery', 'kylin', 'ignite', 'sundb', 'h2', 'h2-legacy', 'snowflake', 'trino', 'spark',
|
||||
'bigquery', 'kylin', 'ignite', 'ignite3', 'sundb', 'h2', 'h2-legacy', 'snowflake', 'trino', 'spark',
|
||||
'db2', 'informix', 'mongodb', 'highgo', 'uxdb', 'yashandb', 'oscar',
|
||||
'iris', 'etcd', 'kafka', 'sqlserver-legacy'
|
||||
]
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"h2-legacy": "0.1.16",
|
||||
"hive": "0.1.49",
|
||||
"ignite": "0.1.0",
|
||||
"ignite3": "0.1.0",
|
||||
"spark": "0.1.18",
|
||||
"informix": "0.1.47",
|
||||
"kingbase": "0.1.49",
|
||||
|
||||
@@ -1235,6 +1235,7 @@ const driverProfiles: Record<
|
||||
},
|
||||
kylin: { type: "kylin", port: 7070, user: "ADMIN", label: "Apache Kylin", icon: "kylin" },
|
||||
ignite: { type: "ignite", port: 10800, user: "", label: "Apache Ignite", icon: "ignite" },
|
||||
ignite3: { type: "ignite3", port: 10800, user: "", label: "Apache Ignite 3", icon: "ignite" },
|
||||
sundb: { type: "sundb", port: 22000, user: "root", label: "科蓝 SUNDB", icon: "sundb" },
|
||||
oscar: { type: "oscar", port: 2003, user: "SYSDBA", label: "神通 OSCAR", icon: "oscar" },
|
||||
jdbc: { type: "jdbc", port: 0, user: "", label: "JDBC", icon: "jdbc" },
|
||||
@@ -2975,6 +2976,7 @@ const iconTypeMap: Record<string, string> = {
|
||||
bigquery: "bigquery",
|
||||
kylin: "kylin",
|
||||
ignite: "ignite",
|
||||
ignite3: "ignite",
|
||||
sundb: "sundb",
|
||||
oscar: "oscar",
|
||||
influxdb: "influxdb",
|
||||
@@ -3057,6 +3059,7 @@ const dbOptions: DbOption[] = [
|
||||
{ value: "bigquery", label: "BigQuery" },
|
||||
{ value: "kylin", label: "Kylin" },
|
||||
{ value: "ignite", label: "Apache Ignite" },
|
||||
{ value: "ignite3", label: "Apache Ignite 3" },
|
||||
{ value: "sundb", label: "科蓝 SUNDB" },
|
||||
{ value: "oscar", label: "神通 OSCAR" },
|
||||
{ value: "xugu", label: "虚谷 XuguDB" },
|
||||
@@ -3095,7 +3098,7 @@ const dbCategoryDefinitions: Array<{
|
||||
{
|
||||
key: "analytics",
|
||||
titleKey: "connection.databaseCategoryAnalytics",
|
||||
optionValues: ["cloudberry", "clickhouse", "doris", "starrocks", "databend", "selectdb", "databricks", "saphana", "teradata", "vertica", "exasol", "redshift", "snowflake", "trino", "prestosql", "hive", "kyuubi", "impala", "spark", "bigquery", "kylin", "ignite", "dremio"],
|
||||
optionValues: ["cloudberry", "clickhouse", "doris", "starrocks", "databend", "selectdb", "databricks", "saphana", "teradata", "vertica", "exasol", "redshift", "snowflake", "trino", "prestosql", "hive", "kyuubi", "impala", "spark", "bigquery", "kylin", "ignite", "ignite3", "dremio"],
|
||||
},
|
||||
{
|
||||
key: "domestic",
|
||||
|
||||
@@ -68,6 +68,7 @@ const assetIcons: Record<string, string> = {
|
||||
trino: "trino",
|
||||
kylin: "apache_kylin",
|
||||
ignite: "apache_ignite",
|
||||
ignite3: "apache_ignite",
|
||||
cockroachdb: "cockroachdb",
|
||||
db2: "db2",
|
||||
dremio: "dremio",
|
||||
|
||||
@@ -89,6 +89,7 @@ describe("AGENT_DRIVER_CATEGORY_MAP integrity", () => {
|
||||
"exasol",
|
||||
"hive",
|
||||
"ignite",
|
||||
"ignite3",
|
||||
"kylin",
|
||||
"phoenix",
|
||||
"prestosql",
|
||||
|
||||
@@ -40,6 +40,7 @@ const DRIVER_STARTUP_FLOOR_TYPES = new Set<DatabaseType>([
|
||||
"bigquery",
|
||||
"kylin",
|
||||
"ignite",
|
||||
"ignite3",
|
||||
"sundb",
|
||||
"oscar",
|
||||
"tdengine",
|
||||
|
||||
@@ -43,6 +43,7 @@ export const AGENT_DRIVER_CATEGORY_MAP: Readonly<Record<string, DriverCategoryKe
|
||||
kingbase: "domestic",
|
||||
kylin: "analytics",
|
||||
ignite: "analytics",
|
||||
ignite3: "analytics",
|
||||
mongodb: "document",
|
||||
neo4j: "graph_ai",
|
||||
"oceanbase-oracle": "domestic",
|
||||
|
||||
@@ -24,6 +24,7 @@ export const SCHEMA_AWARE_TYPES = new Set<DatabaseType>([
|
||||
"jdbc",
|
||||
"h2",
|
||||
"ignite",
|
||||
"ignite3",
|
||||
"snowflake",
|
||||
"trino",
|
||||
"prestosql",
|
||||
@@ -77,6 +78,7 @@ export const TREE_SCHEMA_TYPES = new Set<DatabaseType>([
|
||||
"prestosql",
|
||||
"h2",
|
||||
"ignite",
|
||||
"ignite3",
|
||||
"informix",
|
||||
"xugu",
|
||||
"oscar",
|
||||
|
||||
@@ -77,6 +77,7 @@ export const DATABASE_NAMESPACE_CREATION_MATRIX = {
|
||||
bigquery: { deferred: "dataset creation needs project/location options" },
|
||||
kylin: { deferred: "project/model lifecycle is not SQL database creation" },
|
||||
ignite: { deferred: "schema lifecycle is managed through cluster/cache configuration" },
|
||||
ignite3: { deferred: "schema lifecycle is managed through cluster configuration" },
|
||||
sundb: { deferred: "creation semantics not verified for first pass" },
|
||||
oscar: { database: "schema" },
|
||||
tdengine: { connection: "database" },
|
||||
|
||||
@@ -79,6 +79,7 @@ const DATABASE_TYPE_OBJECTS = new Map<DatabaseType, SidebarObjectKind[]>([
|
||||
["bigquery", TABLE_VIEW_OBJECTS],
|
||||
["kylin", TABLE_VIEW_OBJECTS],
|
||||
["ignite", TABLE_VIEW_OBJECTS],
|
||||
["ignite3", TABLE_VIEW_OBJECTS],
|
||||
["tdengine", TABLE_VIEW_OBJECTS],
|
||||
["iotdb", TABLE_VIEW_OBJECTS],
|
||||
["neo4j", TABLE_VIEW_OBJECTS],
|
||||
|
||||
@@ -77,6 +77,7 @@ export const DATABASE_PROPERTY_EDITING_MATRIX = {
|
||||
bigquery: { deferred: "dataset properties need project/location-specific handling" },
|
||||
kylin: { deferred: "project/model lifecycle is not SQL database property editing" },
|
||||
ignite: { deferred: "schema properties need product-specific handling" },
|
||||
ignite3: { deferred: "schema properties need product-specific handling" },
|
||||
sundb: { deferred: "property semantics not verified for first pass" },
|
||||
oscar: { deferred: "schema properties need product-specific handling" },
|
||||
tdengine: { deferred: "database property editing not verified for first pass" },
|
||||
|
||||
@@ -1148,6 +1148,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
bigquery: "BigQuery",
|
||||
kylin: "Kylin",
|
||||
ignite: "Apache Ignite",
|
||||
ignite3: "Apache Ignite 3",
|
||||
sundb: "科蓝 SUNDB",
|
||||
oscar: "神通 OSCAR",
|
||||
influxdb: "InfluxDB",
|
||||
|
||||
@@ -64,6 +64,7 @@ export type DatabaseType =
|
||||
| "bigquery"
|
||||
| "kylin"
|
||||
| "ignite"
|
||||
| "ignite3"
|
||||
| "sundb"
|
||||
| "oscar"
|
||||
| "tdengine"
|
||||
|
||||
@@ -67,6 +67,7 @@ const BRIDGE_REQUIRED_TYPES: &[&str] = &[
|
||||
"bigquery",
|
||||
"kylin",
|
||||
"ignite",
|
||||
"ignite3",
|
||||
"sundb",
|
||||
"oscar",
|
||||
"xugu",
|
||||
|
||||
@@ -1758,6 +1758,36 @@
|
||||
"driverManagement": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"dbType": "ignite3",
|
||||
"label": "Apache Ignite 3",
|
||||
"runtimeMode": "agent",
|
||||
"mcpMode": "bridge",
|
||||
"agentKey": "ignite3",
|
||||
"singleConnectionPool": false,
|
||||
"metadataConnectionScoped": false,
|
||||
"skipTcpProbe": true,
|
||||
"defaultPort": 10800,
|
||||
"supportLevel": "understand",
|
||||
"capabilities": {
|
||||
"queryExecution": true,
|
||||
"metadataBrowse": true,
|
||||
"objectBrowser": true,
|
||||
"objectSource": false,
|
||||
"schemaSearch": true,
|
||||
"diagram": false,
|
||||
"tableDataEdit": false,
|
||||
"tableStructureEdit": false,
|
||||
"tableImport": false,
|
||||
"dataTransfer": false,
|
||||
"sqlFileExecution": true,
|
||||
"databaseCreate": false,
|
||||
"fieldLineage": false,
|
||||
"sqlExplain": false,
|
||||
"userAdmin": false,
|
||||
"driverManagement": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"dbType": "sundb",
|
||||
"label": "科蓝 SUNDB",
|
||||
|
||||
@@ -267,6 +267,13 @@ const AGENT_CATALOG: &[AgentCatalogEntry] = &[
|
||||
store_visible: true,
|
||||
profiles: &[],
|
||||
},
|
||||
AgentCatalogEntry {
|
||||
db_type: DatabaseType::Ignite3,
|
||||
key: "ignite3",
|
||||
label: "Apache Ignite 3",
|
||||
store_visible: true,
|
||||
profiles: &[],
|
||||
},
|
||||
AgentCatalogEntry {
|
||||
db_type: DatabaseType::Sundb,
|
||||
key: "sundb",
|
||||
|
||||
@@ -222,6 +222,7 @@ macro_rules! agent_connection_pool_database_type {
|
||||
| DatabaseType::Bigquery
|
||||
| DatabaseType::Kylin
|
||||
| DatabaseType::Ignite
|
||||
| DatabaseType::Ignite3
|
||||
| DatabaseType::Sundb
|
||||
| DatabaseType::Oscar
|
||||
| DatabaseType::Tdengine
|
||||
|
||||
@@ -601,6 +601,7 @@ pub enum DatabaseType {
|
||||
#[serde(rename = "questdb")]
|
||||
Questdb,
|
||||
Ignite,
|
||||
Ignite3,
|
||||
Jdbc,
|
||||
/// Message queue admin connection (Pulsar / Kafka / RocketMQ). The specific
|
||||
/// system is determined by `external_config.systemKind`.
|
||||
@@ -1145,6 +1146,7 @@ impl ConnectionConfig {
|
||||
}
|
||||
DatabaseType::Questdb => format!("questdb://{host}:{port}{db_part}"),
|
||||
DatabaseType::Ignite => format!("ignite://{host}:{port}{db_part}"),
|
||||
DatabaseType::Ignite3 => format!("ignite3://{host}:{port}{db_part}"),
|
||||
DatabaseType::Gbase => format!("gbase://{host}:{port}{db_part}"),
|
||||
DatabaseType::H2 => format!("h2://{host}:{port}{db_part}"),
|
||||
DatabaseType::Snowflake => format!("snowflake://{host}/{db_part}"),
|
||||
@@ -1352,6 +1354,9 @@ impl ConnectionConfig {
|
||||
DatabaseType::Ignite => {
|
||||
format!("ignite://{}:{}@{host}:{port}{db_part}", username, password)
|
||||
}
|
||||
DatabaseType::Ignite3 => {
|
||||
format!("ignite3://{}:{}@{host}:{port}{db_part}", username, password)
|
||||
}
|
||||
DatabaseType::Gbase => {
|
||||
format!("gbase://{}:{}@{host}:{port}{db_part}", username, password)
|
||||
}
|
||||
|
||||
@@ -670,7 +670,7 @@ pub fn profile_for(db_type: DatabaseType) -> DdlDialectProfile {
|
||||
ClickHouse => clickhouse_profile(),
|
||||
|
||||
DuckDb | Questdb | SapHana | Teradata | Snowflake | Trino | PrestoSql | Hive | Kyuubi | Impala | Spark
|
||||
| Db2 | Informix | Bigquery | Kylin | Ignite | Oscar | Tdengine | Iotdb | Databricks | Jdbc => {
|
||||
| Db2 | Informix | Bigquery | Kylin | Ignite | Ignite3 | Oscar | Tdengine | Iotdb | Databricks | Jdbc => {
|
||||
conservative_ansi(db_type)
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ export const databaseSupport: DatabaseSupportItem[] = [
|
||||
{ id: "cassandra", name: "Cassandra", icon: "/icons/database/cassandra.svg", tone: "#1287b1" },
|
||||
{ id: "kylin", name: "Kylin", icon: "/icons/database/apache_kylin.svg", tone: "#fb8c00" },
|
||||
{ id: "ignite", name: "Ignite", icon: "/icons/database/apache_ignite.svg", tone: "#ef2c2c" },
|
||||
{ id: "ignite3", name: "Ignite 3", icon: "/icons/database/apache_ignite.svg", tone: "#ef2c2c" },
|
||||
{ id: "dremio", name: "Dremio", icon: "/icons/database/dremio.svg", tone: "#30bdbe" },
|
||||
{ id: "oscar", name: "OSCAR", icon: "/icons/database/oscar.png", tone: "#1b8dff" },
|
||||
{ id: "influxdb", name: "InfluxDB", icon: "/icons/database/influxdb.svg", tone: "#22adf6" },
|
||||
|
||||
@@ -120,6 +120,7 @@ const driverLabels: Record<string, string> = {
|
||||
highgo: "HighGo",
|
||||
hive: "Apache Hive",
|
||||
ignite: "Apache Ignite",
|
||||
ignite3: "Apache Ignite 3",
|
||||
informix: "Informix",
|
||||
iotdb: "Apache IoTDB",
|
||||
iris: "InterSystems IRIS",
|
||||
|
||||
Reference in New Issue
Block a user