diff --git a/meta/src/main/java/com/databasir/core/meta/provider/MetaProviders.java b/meta/src/main/java/com/databasir/core/meta/provider/MetaProviders.java index 8f798c7..709de74 100644 --- a/meta/src/main/java/com/databasir/core/meta/provider/MetaProviders.java +++ b/meta/src/main/java/com/databasir/core/meta/provider/MetaProviders.java @@ -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); + } } diff --git a/meta/src/main/java/com/databasir/core/meta/provider/mysql/MysqlTableTriggerMetaProvider.java b/meta/src/main/java/com/databasir/core/meta/provider/mysql/MysqlTableTriggerMetaProvider.java index 290848f..03144a4 100644 --- a/meta/src/main/java/com/databasir/core/meta/provider/mysql/MysqlTableTriggerMetaProvider.java +++ b/meta/src/main/java/com/databasir/core/meta/provider/mysql/MysqlTableTriggerMetaProvider.java @@ -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(); } } diff --git a/meta/src/main/java/com/databasir/core/meta/provider/sqlserver/SqlServerColumnMetaProvider.java b/meta/src/main/java/com/databasir/core/meta/provider/sqlserver/SqlServerColumnMetaProvider.java new file mode 100644 index 0000000..070f36d --- /dev/null +++ b/meta/src/main/java/com/databasir/core/meta/provider/sqlserver/SqlServerColumnMetaProvider.java @@ -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 selectColumns(Connection connection, TableCondition condition) { + Map 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 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 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; + } +} diff --git a/meta/src/main/java/com/databasir/core/meta/provider/sqlserver/SqlServerTableMetaProvider.java b/meta/src/main/java/com/databasir/core/meta/provider/sqlserver/SqlServerTableMetaProvider.java new file mode 100644 index 0000000..9007c2b --- /dev/null +++ b/meta/src/main/java/com/databasir/core/meta/provider/sqlserver/SqlServerTableMetaProvider.java @@ -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 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 doSelect(ResultSet tablesResult, + Connection connection, + Condition condition) throws SQLException { + List 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 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; + } + +} diff --git a/meta/src/main/java/com/databasir/core/meta/provider/sqlserver/SqlServerTriggerMetaProvider.java b/meta/src/main/java/com/databasir/core/meta/provider/sqlserver/SqlServerTriggerMetaProvider.java new file mode 100644 index 0000000..b3a8acf --- /dev/null +++ b/meta/src/main/java/com/databasir/core/meta/provider/sqlserver/SqlServerTriggerMetaProvider.java @@ -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 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 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; + } + +}