diff --git a/libs/db-browser/pom.xml b/libs/db-browser/pom.xml
index da75aab2fc..b4bc9adb6d 100644
--- a/libs/db-browser/pom.xml
+++ b/libs/db-browser/pom.xml
@@ -107,6 +107,7 @@
21.1.0.0
2.8.0
42.7.3
+ 12.8.2.jre8
@@ -226,6 +227,11 @@
postgresql
${postgres.jdbc.version}
+
+ com.microsoft.sqlserver
+ mssql-jdbc
+ ${mssql.jdbc.version}
+
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/AbstractDBBrowserFactory.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/AbstractDBBrowserFactory.java
index 104019e283..60b2c62053 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/AbstractDBBrowserFactory.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/AbstractDBBrowserFactory.java
@@ -44,6 +44,8 @@ public T create() {
return buildForOdpSharding();
case POSTGRESQL:
return buildForPostgres();
+ case SQL_SERVER:
+ return buildForSqlServer();
default:
throw new IllegalStateException("Not supported for the type, " + type);
}
@@ -63,4 +65,6 @@ public T create() {
public abstract T buildForPostgres();
+ public abstract T buildForSqlServer();
+
}
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/DBBrowserFactory.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/DBBrowserFactory.java
index eaddb91c90..f4e8539985 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/DBBrowserFactory.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/DBBrowserFactory.java
@@ -24,6 +24,7 @@ public interface DBBrowserFactory {
String DORIS = "DORIS";
String ODP_SHARDING_OB_MYSQL = "ODP_SHARDING_OB_MYSQL";
String POSTGRESQL = "POSTGRESQL";
+ String SQL_SERVER = "SQL_SERVER";
T create();
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBMViewEditorFactory.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBMViewEditorFactory.java
index b68e9f4cbc..954ebff27c 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBMViewEditorFactory.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBMViewEditorFactory.java
@@ -70,6 +70,11 @@ public DBMViewEditor buildForPostgres() {
throw new UnsupportedOperationException("Not supported yet");
}
+ @Override
+ public DBMViewEditor buildForSqlServer() {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
+
private DBTableIndexEditor getMViewIndexEditor() {
DBMViewIndexEditorFactory indexFactory = new DBMViewIndexEditorFactory();
indexFactory.setType(this.type);
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBMViewIndexEditorFactory.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBMViewIndexEditorFactory.java
index 49a4b47ad7..20a1133513 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBMViewIndexEditorFactory.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBMViewIndexEditorFactory.java
@@ -60,4 +60,9 @@ public DBTableIndexEditor buildForOdpSharding() {
public DBTableIndexEditor buildForPostgres() {
throw new UnsupportedOperationException("Not supported yet");
}
+
+ @Override
+ public DBTableIndexEditor buildForSqlServer() {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
}
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBObjectOperatorFactory.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBObjectOperatorFactory.java
index 4a027b9b40..5bf6d726c4 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBObjectOperatorFactory.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBObjectOperatorFactory.java
@@ -69,6 +69,11 @@ public DBObjectOperator buildForPostgres() {
throw new UnsupportedOperationException("Not supported yet");
}
+ @Override
+ public DBObjectOperator buildForSqlServer() {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
+
private JdbcOperations getJdbcOperations() {
if (this.jdbcOperations != null) {
return this.jdbcOperations;
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBSequenceEditorFactory.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBSequenceEditorFactory.java
index bb561fa582..cb2e69bb45 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBSequenceEditorFactory.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBSequenceEditorFactory.java
@@ -56,4 +56,9 @@ public DBObjectEditor buildForPostgres() {
throw new UnsupportedOperationException("Not supported yet");
}
+ @Override
+ public DBObjectEditor buildForSqlServer() {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
+
}
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBSynonymEditorFactory.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBSynonymEditorFactory.java
index c44b9ee533..337f5ee9a6 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBSynonymEditorFactory.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBSynonymEditorFactory.java
@@ -56,4 +56,9 @@ public DBObjectEditor buildForPostgres() {
throw new UnsupportedOperationException("Not supported yet");
}
+ @Override
+ public DBObjectEditor buildForSqlServer() {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
+
}
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableColumnEditorFactory.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableColumnEditorFactory.java
index e4cd7329ad..fe29e3061b 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableColumnEditorFactory.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableColumnEditorFactory.java
@@ -56,4 +56,9 @@ public DBTableColumnEditor buildForPostgres() {
throw new UnsupportedOperationException("Not supported yet");
}
+ @Override
+ public DBTableColumnEditor buildForSqlServer() {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
+
}
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableConstraintEditorFactory.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableConstraintEditorFactory.java
index 18dd00ae0e..2e01b46101 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableConstraintEditorFactory.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableConstraintEditorFactory.java
@@ -76,4 +76,9 @@ public DBTableConstraintEditor buildForPostgres() {
throw new UnsupportedOperationException("Not supported yet");
}
+ @Override
+ public DBTableConstraintEditor buildForSqlServer() {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
+
}
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableEditorFactory.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableEditorFactory.java
index 19103a4926..52761cd88e 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableEditorFactory.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableEditorFactory.java
@@ -83,6 +83,11 @@ public DBTableEditor buildForPostgres() {
throw new UnsupportedOperationException("Not supported yet");
}
+ @Override
+ public DBTableEditor buildForSqlServer() {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
+
private DBTableIndexEditor getTableIndexEditor() {
DBTableIndexEditorFactory indexFactory = new DBTableIndexEditorFactory();
indexFactory.setType(this.type);
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableIndexEditorFactory.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableIndexEditorFactory.java
index 85451e17fc..3a9c38c095 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableIndexEditorFactory.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTableIndexEditorFactory.java
@@ -20,6 +20,7 @@
import com.oceanbase.tools.dbbrowser.editor.mysql.OBMySQLIndexEditor;
import com.oceanbase.tools.dbbrowser.editor.oracle.OBOracleIndexEditor;
import com.oceanbase.tools.dbbrowser.editor.oracle.OracleIndexEditor;
+import com.oceanbase.tools.dbbrowser.editor.sqlserver.SqlServerIndexEditor;
import lombok.Setter;
import lombok.experimental.Accessors;
@@ -63,4 +64,9 @@ public DBTableIndexEditor buildForPostgres() {
throw new UnsupportedOperationException("Not supported yet");
}
+ @Override
+ public DBTableIndexEditor buildForSqlServer() {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
+
}
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTablePartitionEditorFactory.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTablePartitionEditorFactory.java
index 276a4161f4..0633bea916 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTablePartitionEditorFactory.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/editor/DBTablePartitionEditorFactory.java
@@ -81,4 +81,9 @@ public DBTablePartitionEditor buildForPostgres() {
throw new UnsupportedOperationException("Not supported yet");
}
+ @Override
+ public DBTablePartitionEditor buildForSqlServer() {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
+
}
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/model/DBConstraintType.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/model/DBConstraintType.java
index f97b0fb458..af005ce11a 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/model/DBConstraintType.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/model/DBConstraintType.java
@@ -25,6 +25,7 @@ public enum DBConstraintType {
INDEX("INDEX"),
CHECK("CHECK", "C"),
NOT_NULL("NOT NULL", "NOT_NULL"),
+ UNIQUE("UNIQUE"),
UNKNOWN("UNKNOWN"),
;
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/model/DBIndexType.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/model/DBIndexType.java
index 3e14a33a7f..439f960870 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/model/DBIndexType.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/model/DBIndexType.java
@@ -28,6 +28,7 @@ public enum DBIndexType {
FUNCTION_BASED_BITMAP("FUNCTION-BASED BITMAP"),
DOMAIN("DOMAIN"),
SPATIAL("SPATIAL"),
+ CLUSTERED("CLUSTERED"),
UNKNOWN("UNKNOWN");
private String value;
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/schema/DBSchemaAccessorFactory.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/schema/DBSchemaAccessorFactory.java
index bef1ff9411..15d13807b6 100644
--- a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/schema/DBSchemaAccessorFactory.java
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/schema/DBSchemaAccessorFactory.java
@@ -43,6 +43,7 @@
import com.oceanbase.tools.dbbrowser.schema.oracle.OBOracleSchemaAccessor;
import com.oceanbase.tools.dbbrowser.schema.oracle.OracleSchemaAccessor;
import com.oceanbase.tools.dbbrowser.schema.postgre.PostgresSchemaAccessor;
+import com.oceanbase.tools.dbbrowser.schema.sqlserver.SqlServerSchemaAccessor;
import com.oceanbase.tools.dbbrowser.util.ALLDataDictTableNames;
import com.oceanbase.tools.dbbrowser.util.VersionUtils;
@@ -161,6 +162,11 @@ public DBSchemaAccessor buildForPostgres() {
return new PostgresSchemaAccessor(getJdbcOperations());
}
+ @Override
+ public DBSchemaAccessor buildForSqlServer() {
+ return new SqlServerSchemaAccessor(getJdbcOperations());
+ }
+
private JdbcOperations getJdbcOperations() {
if (this.jdbcOperations != null) {
return this.jdbcOperations;
diff --git a/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/schema/sqlserver/SqlServerSchemaAccessor.java b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/schema/sqlserver/SqlServerSchemaAccessor.java
new file mode 100644
index 0000000000..19874bd45d
--- /dev/null
+++ b/libs/db-browser/src/main/java/com/oceanbase/tools/dbbrowser/schema/sqlserver/SqlServerSchemaAccessor.java
@@ -0,0 +1,1782 @@
+/*
+ * Copyright (c) 2025 OceanBase.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.oceanbase.tools.dbbrowser.schema.sqlserver;
+
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import org.springframework.jdbc.BadSqlGrammarException;
+import org.springframework.jdbc.core.JdbcOperations;
+
+import com.oceanbase.tools.dbbrowser.model.DBColumnGroupElement;
+import com.oceanbase.tools.dbbrowser.model.DBConstraintType;
+import com.oceanbase.tools.dbbrowser.model.DBDatabase;
+import com.oceanbase.tools.dbbrowser.model.DBFunction;
+import com.oceanbase.tools.dbbrowser.model.DBIndexType;
+import com.oceanbase.tools.dbbrowser.model.DBMViewRefreshParameter;
+import com.oceanbase.tools.dbbrowser.model.DBMViewRefreshRecord;
+import com.oceanbase.tools.dbbrowser.model.DBMViewRefreshRecordParam;
+import com.oceanbase.tools.dbbrowser.model.DBMaterializedView;
+import com.oceanbase.tools.dbbrowser.model.DBObjectIdentity;
+import com.oceanbase.tools.dbbrowser.model.DBObjectType;
+import com.oceanbase.tools.dbbrowser.model.DBPLObjectIdentity;
+import com.oceanbase.tools.dbbrowser.model.DBPLParam;
+import com.oceanbase.tools.dbbrowser.model.DBPLParamMode;
+import com.oceanbase.tools.dbbrowser.model.DBPackage;
+import com.oceanbase.tools.dbbrowser.model.DBProcedure;
+import com.oceanbase.tools.dbbrowser.model.DBSequence;
+import com.oceanbase.tools.dbbrowser.model.DBSynonym;
+import com.oceanbase.tools.dbbrowser.model.DBSynonymType;
+import com.oceanbase.tools.dbbrowser.model.DBTable;
+import com.oceanbase.tools.dbbrowser.model.DBTable.DBTableOptions;
+import com.oceanbase.tools.dbbrowser.model.DBTableColumn;
+import com.oceanbase.tools.dbbrowser.model.DBTableConstraint;
+import com.oceanbase.tools.dbbrowser.model.DBTableIndex;
+import com.oceanbase.tools.dbbrowser.model.DBTablePartition;
+import com.oceanbase.tools.dbbrowser.model.DBTablePartitionDefinition;
+import com.oceanbase.tools.dbbrowser.model.DBTablePartitionOption;
+import com.oceanbase.tools.dbbrowser.model.DBTableSubpartitionDefinition;
+import com.oceanbase.tools.dbbrowser.model.DBTrigger;
+import com.oceanbase.tools.dbbrowser.model.DBType;
+import com.oceanbase.tools.dbbrowser.model.DBVariable;
+import com.oceanbase.tools.dbbrowser.model.DBView;
+import com.oceanbase.tools.dbbrowser.model.DBViewCheckOption;
+import com.oceanbase.tools.dbbrowser.schema.DBSchemaAccessor;
+import com.oceanbase.tools.dbbrowser.util.StringUtils;
+
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class SqlServerSchemaAccessor implements DBSchemaAccessor {
+
+ protected JdbcOperations jdbcOperations;
+
+ public SqlServerSchemaAccessor(@NonNull JdbcOperations jdbcOperations) {
+ this.jdbcOperations = jdbcOperations;
+ }
+
+ @Override
+ public List showDatabases() {
+ String sql = "SELECT name FROM sys.databases;";
+ try {
+ return jdbcOperations.queryForList(sql, String.class);
+ } catch (BadSqlGrammarException e) {
+ log.warn("Failed to query databases", e);
+ return Collections.emptyList();
+ }
+ }
+
+ @Override
+ public DBDatabase getDatabase(String schemaName) {
+ // 注意:在 SQL Server 中,schemaName 参数实际表示数据库名(database name)
+ // SQL Server 的层次结构:databases -> schemas -> tables
+ DBDatabase database = new DBDatabase();
+ database.setId(schemaName);
+ database.setName(schemaName);
+ // SQL Server 中获取指定数据库的字符集和排序规则
+ String sql = "SELECT "
+ + " collation_name AS collation "
+ + "FROM sys.databases "
+ + "WHERE name = ?";
+ AtomicReference collation = new AtomicReference<>();
+ try {
+ jdbcOperations.query(sql, new Object[] {schemaName}, rs -> {
+ if (rs.next()) {
+ collation.set(rs.getString(1));
+ }
+ });
+ database.setCollation(collation.get());
+ } catch (Exception e) {
+ log.warn("Failed to get database collation for database: " + schemaName, e);
+ }
+ return database;
+ }
+
+ @Override
+ public List listDatabases() {
+ // SQL Server 中返回所有数据库及其信息
+ String sql = "SELECT "
+ + " name, "
+ + " collation_name AS collation "
+ + "FROM sys.databases "
+ + "WHERE state_desc = 'ONLINE' "
+ + "ORDER BY name";
+ try {
+ return jdbcOperations.query(sql, (rs, rowNum) -> {
+ DBDatabase database = new DBDatabase();
+ database.setId(rs.getString("name"));
+ database.setName(rs.getString("name"));
+ database.setCollation(rs.getString("collation"));
+ return database;
+ });
+ } catch (Exception e) {
+ log.warn("Failed to list databases", e);
+ return Collections.emptyList();
+ }
+ }
+
+ @Override
+ public void switchDatabase(String schemaName) {
+ // SQL Server 使用 USE 语句切换数据库
+ // 注意:参数名是 schemaName,但在 SQL Server 中实际是数据库名
+ String sql = "USE [" + schemaName.replace("]", "]]") + "]";
+ try {
+ jdbcOperations.execute(sql);
+ } catch (Exception e) {
+ log.error("Failed to switch database: " + schemaName, e);
+ throw new RuntimeException("Failed to switch database: " + schemaName, e);
+ }
+ }
+
+ @Override
+ public List listUsers() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * 解析 schemaName 参数,支持两种格式: 1. "database" - 只有数据库名,默认使用 dbo schema 2. "database.schema" - 数据库名和
+ * schema 名
+ *
+ * @param schemaName 可能是数据库名或 database.schema 格式
+ * @return [databaseName, schemaName]
+ */
+ private String[] parseDatabaseAndSchema(String schemaName) {
+ if (schemaName == null) {
+ return new String[] {"", "dbo"};
+ }
+
+ if (schemaName.contains(".")) {
+ String[] parts = schemaName.split("\\.", 2);
+ return new String[] {parts[0], parts[1]};
+ } else {
+ // 兼容旧代码:只有数据库名,默认使用 dbo
+ return new String[] {schemaName, "dbo"};
+ }
+ }
+
+ @Override
+ public List showTables(String schemaName) {
+ return showTablesLike(schemaName, null);
+ }
+
+ @Override
+ public List showTablesLike(String schemaName, String tableNameLike) {
+ // SQL Server 的层次结构:databases -> schemas -> tables
+ // schemaName 参数支持两种格式:
+ // 1. "database" - 只有数据库名,默认使用 dbo schema
+ // 2. "database.schema" - 数据库名和 schema 名
+ String[] dbAndSchema = parseDatabaseAndSchema(schemaName);
+ String databaseName = dbAndSchema[0];
+ String actualSchemaName = dbAndSchema[1];
+
+ // 确保在正确的数据库中查询
+ String currentDb = null;
+ try {
+ currentDb = jdbcOperations.queryForObject("SELECT DB_NAME()", String.class);
+ if (!databaseName.equals(currentDb)) {
+ switchDatabase(databaseName);
+ }
+ } catch (Exception e) {
+ log.warn("Failed to switch to database: " + databaseName, e);
+ return Collections.emptyList();
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("SELECT table_name FROM information_schema.tables ");
+ sb.append("WHERE table_catalog = ? ");
+ sb.append("AND table_schema = ? ");
+ sb.append("AND table_type = 'BASE TABLE'");
+
+ List