mirror of
				https://github.com/vran-dev/databasir.git
				synced 2025-10-31 20:49:22 +08:00 
			
		
		
		
	feat: rewrite sqlserver meta provider (#157)
This commit is contained in:
		| @@ -2,6 +2,9 @@ package com.databasir.core.meta.provider; | ||||
|  | ||||
| import com.databasir.core.meta.provider.jdbc.*; | ||||
| import com.databasir.core.meta.provider.mysql.MysqlTableTriggerMetaProvider; | ||||
| import com.databasir.core.meta.provider.sqlserver.SqlServerColumnMetaProvider; | ||||
| import com.databasir.core.meta.provider.sqlserver.SqlServerTableMetaProvider; | ||||
| import com.databasir.core.meta.provider.sqlserver.SqlServerTriggerMetaProvider; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| import java.sql.Connection; | ||||
| @@ -35,7 +38,7 @@ public class MetaProviders { | ||||
|             return jdbc(); | ||||
|         } | ||||
|         if (url.contains(":sqlserver:")) { | ||||
|             return jdbc(); | ||||
|             return sqlServer(); | ||||
|         } | ||||
|         if (url.contains(":mysql:")) { | ||||
|             return mysql(); | ||||
| @@ -57,4 +60,17 @@ public class MetaProviders { | ||||
|         return new JdbcDatabaseMetaProvider(tableMetaProvider); | ||||
|     } | ||||
|  | ||||
|     private static DatabaseMetaProvider sqlServer() { | ||||
|         var columnMetaProvider = new SqlServerColumnMetaProvider(); | ||||
|         var foreignKeyMetaProvider = new JdbcForeignKeyMetaProvider(); | ||||
|         var indexMetaProvider = new JdbcIndexMetaProvider(); | ||||
|         var triggerMetaProvider = new SqlServerTriggerMetaProvider(); | ||||
|         var tableMetaProvider = new SqlServerTableMetaProvider( | ||||
|                 columnMetaProvider, | ||||
|                 indexMetaProvider, | ||||
|                 foreignKeyMetaProvider, | ||||
|                 triggerMetaProvider | ||||
|         ); | ||||
|         return new JdbcDatabaseMetaProvider(tableMetaProvider); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -61,7 +61,7 @@ public class MysqlTableTriggerMetaProvider implements TriggerMetaProvider { | ||||
|             } | ||||
|             return triggers; | ||||
|         } catch (SQLException e) { | ||||
|             log.warn("create trigger doc failed", e); | ||||
|             log.warn("get trigger meta failed", e); | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,78 @@ | ||||
| package com.databasir.core.meta.provider.sqlserver; | ||||
|  | ||||
| import com.databasir.core.meta.data.ColumnMeta; | ||||
| import com.databasir.core.meta.provider.ColumnMetaProvider; | ||||
| import com.databasir.core.meta.provider.condition.TableCondition; | ||||
| import com.databasir.core.meta.provider.jdbc.JdbcColumnMetaProvider; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| import java.sql.Connection; | ||||
| import java.sql.PreparedStatement; | ||||
| import java.sql.ResultSet; | ||||
| import java.sql.SQLException; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| @Slf4j | ||||
| public class SqlServerColumnMetaProvider implements ColumnMetaProvider { | ||||
|  | ||||
|     private final ColumnMetaProvider columnMetaProvider = new JdbcColumnMetaProvider(); | ||||
|  | ||||
|     @Override | ||||
|     public List<ColumnMeta> selectColumns(Connection connection, TableCondition condition) { | ||||
|         Map<String, String> columnRemarksMap = getColumnRemarks(connection, condition); | ||||
|         return columnMetaProvider.selectColumns(connection, condition) | ||||
|                 .stream() | ||||
|                 .map(column -> { | ||||
|                     String remark = columnRemarksMap.getOrDefault(column.getName(), ""); | ||||
|                     column.setComment(remark); | ||||
|                     return column; | ||||
|                 }) | ||||
|                 .collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     private Map<String, String> getColumnRemarks(Connection connection, | ||||
|                                                  TableCondition condition) { | ||||
|         String sql = "SELECT col.name AS COLUMN_NAME,\n" + | ||||
|                 "       ep.value AS REMARKS\n" + | ||||
|                 "FROM sys.tables AS tab\n" + | ||||
|                 "         INNER JOIN sys.columns AS col\n" + | ||||
|                 "                    ON tab.object_id = col.object_id\n" + | ||||
|                 "         LEFT JOIN sys.extended_properties AS ep " + | ||||
|                 "ON ep.major_id = col.object_id AND ep.minor_id = col.column_id\n" + | ||||
|                 "WHERE tab.name LIKE ?\n" + | ||||
|                 "  AND SCHEMA_NAME(tab.schema_id) LIKE ?\n" + | ||||
|                 "ORDER BY tab.name, column_id;"; | ||||
|  | ||||
|         Map<String, String> map = new HashMap<>(); | ||||
|         PreparedStatement preparedStatement = null; | ||||
|         try { | ||||
|             preparedStatement = connection.prepareStatement(sql); | ||||
|             preparedStatement.setString(1, condition.getTableName()); | ||||
|             preparedStatement.setString(2, condition.getSchemaName()); | ||||
|             ResultSet result = preparedStatement.executeQuery(); | ||||
|             while (result.next()) { | ||||
|                 String name = result.getString("COLUMN_NAME"); | ||||
|                 String remarks = result.getString("REMARKS"); | ||||
|                 if (name == null || remarks == null) { | ||||
|                     continue; | ||||
|                 } else { | ||||
|                     map.put(name, remarks); | ||||
|                 } | ||||
|             } | ||||
|         } catch (SQLException e) { | ||||
|             log.error("", e); | ||||
|         } finally { | ||||
|             if (preparedStatement != null) { | ||||
|                 try { | ||||
|                     preparedStatement.close(); | ||||
|                 } catch (SQLException e) { | ||||
|                     // ignore | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return map; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,107 @@ | ||||
| package com.databasir.core.meta.provider.sqlserver; | ||||
|  | ||||
| import com.databasir.core.meta.data.ColumnMeta; | ||||
| import com.databasir.core.meta.data.TableMeta; | ||||
| import com.databasir.core.meta.provider.*; | ||||
| import com.databasir.core.meta.provider.condition.Condition; | ||||
| import com.databasir.core.meta.provider.condition.TableCondition; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| import java.sql.Connection; | ||||
| import java.sql.PreparedStatement; | ||||
| import java.sql.ResultSet; | ||||
| import java.sql.SQLException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| @RequiredArgsConstructor | ||||
| @Slf4j | ||||
| public class SqlServerTableMetaProvider implements TableMetaProvider { | ||||
|  | ||||
|     private final ColumnMetaProvider columnMetaProvider; | ||||
|  | ||||
|     private final IndexMetaProvider indexMetaProvider; | ||||
|  | ||||
|     private final ForeignKeyMetaProvider foreignKeyMetaProvider; | ||||
|  | ||||
|     private final TriggerMetaProvider triggerMetaProvider; | ||||
|  | ||||
|     @Override | ||||
|     public List<TableMeta> selectTables(Connection connection, Condition condition) { | ||||
|         String sql = "SELECT sys.objects.name                            AS TABLE_NAME,\n" + | ||||
|                 "       sys.objects.type_desc                            AS TABLE_TYPE,\n" + | ||||
|                 "       CAST(extended_properties.value AS NVARCHAR(500)) AS REMARKS\n" + | ||||
|                 "FROM sys.objects\n" + | ||||
|                 "         LEFT JOIN sys.schemas ON sys.objects.schema_id = sys.schemas.schema_id\n" + | ||||
|                 "         LEFT JOIN sys.extended_properties " + | ||||
|                 "ON sys.objects.object_id = sys.extended_properties.major_id\n" + | ||||
|                 "WHERE (type = 'U' OR type = 'V')\n" + | ||||
|                 "  AND sys.extended_properties.minor_id = 0\n" + | ||||
|                 "  AND sys.schemas.name LIKE ?;\n"; | ||||
|         PreparedStatement preparedStatement = null; | ||||
|         try { | ||||
|             preparedStatement = connection.prepareStatement(sql); | ||||
|             preparedStatement.setString(1, condition.getSchemaName()); | ||||
|             ResultSet results = preparedStatement.executeQuery(); | ||||
|             return doSelect(results, connection, condition); | ||||
|         } catch (SQLException e) { | ||||
|             log.warn("get table meta failed {}", e.getMessage()); | ||||
|             if (log.isDebugEnabled()) { | ||||
|                 log.debug("get table meta failed", e); | ||||
|             } | ||||
|             return Collections.emptyList(); | ||||
|         } finally { | ||||
|             if (preparedStatement != null) { | ||||
|                 try { | ||||
|                     preparedStatement.close(); | ||||
|                 } catch (SQLException e) { | ||||
|                     // ignore | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private List<TableMeta> doSelect(ResultSet tablesResult, | ||||
|                                      Connection connection, | ||||
|                                      Condition condition) throws SQLException { | ||||
|         List<TableMeta> tableMetas = new ArrayList<>(); | ||||
|         try { | ||||
|             while (tablesResult.next()) { | ||||
|                 String tableName = tablesResult.getString("TABLE_NAME"); | ||||
|                 if (condition.tableIsIgnored(tableName)) { | ||||
|                     if (log.isWarnEnabled()) { | ||||
|                         log.warn("ignored table: " + condition.getSchemaName() + "." + tableName); | ||||
|                     } | ||||
|                 } else { | ||||
|                     String tableType = tablesResult.getString("TABLE_TYPE"); | ||||
|                     String tableComment = tablesResult.getString("REMARKS"); | ||||
|                     TableCondition tableCondition = TableCondition.of(condition, tableName); | ||||
|                     List<ColumnMeta> columns = columnMetaProvider.selectColumns(connection, tableCondition); | ||||
|                     if (columns.isEmpty()) { | ||||
|                         if (log.isWarnEnabled()) { | ||||
|                             log.warn("ignored table: " + condition.getSchemaName() + "." + tableName | ||||
|                                     + ", caused by get empty columns"); | ||||
|                         } | ||||
|                         continue; | ||||
|                     } | ||||
|                     TableMeta tableMeta = TableMeta.builder() | ||||
|                             .name(tableName) | ||||
|                             .type(tableType) | ||||
|                             .comment(tableComment) | ||||
|                             .columns(columns) | ||||
|                             .foreignKeys(foreignKeyMetaProvider.selectForeignKeys(connection, tableCondition)) | ||||
|                             .indexes(indexMetaProvider.selectIndexes(connection, tableCondition)) | ||||
|                             .triggers(triggerMetaProvider.selectTriggers(connection, tableCondition)) | ||||
|                             .build(); | ||||
|                     tableMetas.add(tableMeta); | ||||
|                 } | ||||
|             } | ||||
|         } finally { | ||||
|             tablesResult.close(); | ||||
|         } | ||||
|         return tableMetas; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,100 @@ | ||||
| package com.databasir.core.meta.provider.sqlserver; | ||||
|  | ||||
| import com.databasir.core.meta.data.TriggerMeta; | ||||
| import com.databasir.core.meta.provider.TriggerMetaProvider; | ||||
| import com.databasir.core.meta.provider.condition.TableCondition; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| import java.sql.Connection; | ||||
| import java.sql.PreparedStatement; | ||||
| import java.sql.ResultSet; | ||||
| import java.sql.SQLException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| @Slf4j | ||||
| public class SqlServerTriggerMetaProvider implements TriggerMetaProvider { | ||||
|  | ||||
|     private static final Pattern DATE_TIME_PATTERN = | ||||
|             Pattern.compile("(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})(.*)"); | ||||
|  | ||||
|     @Override | ||||
|     public List<TriggerMeta> selectTriggers(Connection connection, TableCondition condition) { | ||||
|         String sql = "SELECT SCHEMA_NAME(tab.schema_id) + '.' + tab.name AS table_name,\n" + | ||||
|                 "       trig.name                                   AS trigger_name,\n" + | ||||
|                 "       trig.create_date                            AS create_date,\n" + | ||||
|                 "       CASE\n" + | ||||
|                 "           WHEN is_instead_of_trigger = 1 THEN 'Instead of'\n" + | ||||
|                 "           ELSE 'After' END                        AS timing,\n" + | ||||
|                 "       (CASE\n" + | ||||
|                 "            WHEN OBJECTPROPERTY(trig.object_id, 'ExecIsUpdateTrigger') = 1\n" + | ||||
|                 "                THEN 'Update '\n" + | ||||
|                 "            ELSE '' END\n" + | ||||
|                 "           + CASE\n" + | ||||
|                 "                 WHEN OBJECTPROPERTY(trig.object_id, 'ExecIsDeleteTrigger') = 1\n" + | ||||
|                 "                     THEN 'Delete '\n" + | ||||
|                 "                 ELSE '' END\n" + | ||||
|                 "           + CASE\n" + | ||||
|                 "                 WHEN OBJECTPROPERTY(trig.object_id, 'ExecIsInsertTrigger') = 1\n" + | ||||
|                 "                     THEN 'Insert '\n" + | ||||
|                 "                 ELSE '' END\n" + | ||||
|                 "           )                                       AS manipulation,\n" + | ||||
|                 "       CASE\n" + | ||||
|                 "           WHEN trig.[type] = 'TA' THEN 'Assembly (CLR) trigger'\n" + | ||||
|                 "           WHEN trig.[type] = 'TR' THEN 'SQL trigger'\n" + | ||||
|                 "           ELSE '' END                             AS [TYPE],\n" + | ||||
|                 "       CASE\n" + | ||||
|                 "           WHEN is_disabled = 1 THEN 'Disabled'\n" + | ||||
|                 "           ELSE 'Active' END                       AS [status],\n" + | ||||
|                 "       OBJECT_DEFINITION(trig.object_id)           AS STATEMENT\n" + | ||||
|                 "FROM sys.triggers trig\n" + | ||||
|                 "         INNER JOIN sys.objects tab\n" + | ||||
|                 "                    ON trig.parent_id = tab.object_id\n" + | ||||
|                 "WHERE SCHEMA_NAME(tab.schema_id) = ? AND tab.name = ?"; | ||||
|         PreparedStatement preparedStatement = null; | ||||
|         List<TriggerMeta> triggerMetas = new ArrayList<>(); | ||||
|         try { | ||||
|             preparedStatement = connection.prepareStatement(sql); | ||||
|             preparedStatement.setString(1, condition.getSchemaName()); | ||||
|             preparedStatement.setString(2, condition.getTableName()); | ||||
|             ResultSet resultSet = preparedStatement.executeQuery(); | ||||
|             while (resultSet.next()) { | ||||
|                 String triggerName = resultSet.getString("trigger_name"); | ||||
|                 String timing = resultSet.getString("timing"); | ||||
|                 String manipulation = resultSet.getString("manipulation"); | ||||
|                 String statement = resultSet.getString("statement"); | ||||
|                 Matcher matcher = DATE_TIME_PATTERN.matcher(resultSet.getString("create_date")); | ||||
|                 String createAt; | ||||
|                 if (matcher.matches()) { | ||||
|                     createAt = matcher.group(1); | ||||
|                 } else { | ||||
|                     createAt = "1970-01-01 00:00:00"; | ||||
|                 } | ||||
|                 triggerMetas.add(TriggerMeta.builder() | ||||
|                         .name(triggerName) | ||||
|                         .timing(timing) | ||||
|                         .manipulation(manipulation) | ||||
|                         .statement(statement) | ||||
|                         .createAt(createAt) | ||||
|                         .build()); | ||||
|             } | ||||
|         } catch (SQLException e) { | ||||
|             log.warn("ignore trigger meta by error {}", e.getMessage()); | ||||
|             if (log.isDebugEnabled()) { | ||||
|                 log.debug("ignore trigger meta by error ", e); | ||||
|             } | ||||
|         } finally { | ||||
|             if (preparedStatement != null) { | ||||
|                 try { | ||||
|                     preparedStatement.close(); | ||||
|                 } catch (SQLException e) { | ||||
|                     // ignore | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return triggerMetas; | ||||
|     } | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user