diff --git a/README.md b/README.md index 67f115b..1e1d8c2 100644 --- a/README.md +++ b/README.md @@ -1 +1,37 @@ -# Databasir \ No newline at end of file +# Databasir + +Database document generator + +# Features + +- render as markdown +- render as html (TODO) +- render as PDF (TODO) + +# Quick Start + +```java +// First: get database connection +Class.forName("com.mysql.cj.jdbc.Driver"); +Properties info=new Properties(); +info.put("user","root"); +info.put("password","123456"); +// this config is used by mysql +info.put("useInformationSchema","true"); +String url="jdbc:mysql://localhost:3306/patient?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true"; +var connection=DriverManager.getConnection(url,info); + +// Second: generate doc model +var config=DatabaseDocConfiguration.builder() + .databaseName("patient") + .connection(connection) + .build(); +DatabaseDoc doc = JdbcDatabaseDocFactory.of().create(config).orElseThrow(); + +// Final: Render as markdown +try(FileOutputStream out=new FileOutputStream("doc.md")){ + MarkdownRender.of(new RenderConfiguration()).rendering(doc,out); +}catch(IOException e){ + throw new IllegalStateException(e); +} +``` \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle index eae2759..9a94a40 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,4 +1,5 @@ dependencies { - - + implementation 'mysql:mysql-connector-java:8.0.27' + implementation 'commons-io:commons-io:2.11.0' + implementation 'org.commonmark:commonmark:0.18.1' } diff --git a/core/src/main/java/com/databasir/core/doc/config/DocConfiguration.java b/core/src/main/java/com/databasir/core/doc/config/DocConfiguration.java deleted file mode 100644 index 941333e..0000000 --- a/core/src/main/java/com/databasir/core/doc/config/DocConfiguration.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.databasir.core.doc.config; - -public class DocConfiguration { - -} diff --git a/core/src/main/java/com/databasir/core/doc/factory/DatabaseDocConfiguration.java b/core/src/main/java/com/databasir/core/doc/factory/DatabaseDocConfiguration.java new file mode 100644 index 0000000..1b8b263 --- /dev/null +++ b/core/src/main/java/com/databasir/core/doc/factory/DatabaseDocConfiguration.java @@ -0,0 +1,49 @@ +package com.databasir.core.doc.factory; + +import com.databasir.core.doc.factory.jdbc.*; +import lombok.Builder; +import lombok.Getter; + +import java.sql.Connection; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +@Builder +@Getter +public class DatabaseDocConfiguration { + + private String databaseName; + + private Connection connection; + + @Builder.Default + private List ignoreTableRegexes = Collections.emptyList(); + + @Builder.Default + private List ignoreColumnRegexes = Collections.emptyList(); + + @Builder.Default + private DatabaseDocFactory databaseDocFactory = new JdbcDatabaseDocFactory(); + + @Builder.Default + private TableDocFactory tableDocFactory = new JdbcTableDocFactory(); + + @Builder.Default + private TableIndexDocFactory tableIndexDocFactory = new JdbcTableIndexDocFactory(); + + @Builder.Default + private TableTriggerDocFactory tableTriggerDocFactory = new JdbcTableTriggerDocFactory(); + + @Builder.Default + private TableColumnDocFactory tableColumnDocFactory = new JdbcTableColumnDocFactory(); + + public boolean ignoredTable(String tableName) { + return ignoreTableRegexes.stream().anyMatch(regex -> Pattern.matches(regex, tableName)); + } + + public boolean ignoredColumn(String column) { + return ignoreColumnRegexes.stream().anyMatch(regex -> Pattern.matches(regex, column)); + } + +} diff --git a/core/src/main/java/com/databasir/core/doc/factory/DatabaseDocFactory.java b/core/src/main/java/com/databasir/core/doc/factory/DatabaseDocFactory.java index b36f21a..6e4cdb8 100644 --- a/core/src/main/java/com/databasir/core/doc/factory/DatabaseDocFactory.java +++ b/core/src/main/java/com/databasir/core/doc/factory/DatabaseDocFactory.java @@ -2,11 +2,10 @@ package com.databasir.core.doc.factory; import com.databasir.core.doc.model.DatabaseDoc; -import java.sql.Connection; import java.util.Optional; public interface DatabaseDocFactory extends Sortable { - Optional create(Connection connection, String databaseName); + Optional create(DatabaseDocConfiguration configuration); } diff --git a/core/src/main/java/com/databasir/core/doc/factory/Sortable.java b/core/src/main/java/com/databasir/core/doc/factory/Sortable.java index 6c3c7a7..4d8e4db 100644 --- a/core/src/main/java/com/databasir/core/doc/factory/Sortable.java +++ b/core/src/main/java/com/databasir/core/doc/factory/Sortable.java @@ -5,12 +5,12 @@ public interface Sortable> extends Comparable { /** * @return priority, min -> max means low -> high */ - default int priority() { + default int order() { return Integer.MIN_VALUE; } @Override default int compareTo(T o) { - return Integer.compare(this.priority(), o.priority()); + return Integer.compare(this.order(), o.order()); } } diff --git a/core/src/main/java/com/databasir/core/doc/factory/TableColumnDocFactory.java b/core/src/main/java/com/databasir/core/doc/factory/TableColumnDocFactory.java index ec7d672..698c309 100644 --- a/core/src/main/java/com/databasir/core/doc/factory/TableColumnDocFactory.java +++ b/core/src/main/java/com/databasir/core/doc/factory/TableColumnDocFactory.java @@ -2,9 +2,11 @@ package com.databasir.core.doc.factory; import com.databasir.core.doc.model.ColumnDoc; +import java.sql.DatabaseMetaData; import java.util.List; public interface TableColumnDocFactory extends Sortable { - List create(TableDocCreateContext context); + List create(String tableName, DatabaseMetaData metaData, DatabaseDocConfiguration configuration); + } diff --git a/core/src/main/java/com/databasir/core/doc/factory/TableDocCreateContext.java b/core/src/main/java/com/databasir/core/doc/factory/TableDocCreateContext.java deleted file mode 100644 index c3f0a9a..0000000 --- a/core/src/main/java/com/databasir/core/doc/factory/TableDocCreateContext.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.databasir.core.doc.factory; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.sql.Connection; -import java.sql.DatabaseMetaData; - -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Getter -public class TableDocCreateContext { - - private String database; - - private String tableName; - - private Connection connection; - - private DatabaseMetaData databaseMetaData; - -} diff --git a/core/src/main/java/com/databasir/core/doc/factory/TableDocFactory.java b/core/src/main/java/com/databasir/core/doc/factory/TableDocFactory.java index 1ec52e5..5640404 100644 --- a/core/src/main/java/com/databasir/core/doc/factory/TableDocFactory.java +++ b/core/src/main/java/com/databasir/core/doc/factory/TableDocFactory.java @@ -2,10 +2,11 @@ package com.databasir.core.doc.factory; import com.databasir.core.doc.model.TableDoc; +import java.sql.DatabaseMetaData; import java.util.List; public interface TableDocFactory extends Sortable { - List create(TableDocCreateContext context); + List create(DatabaseMetaData metaData, DatabaseDocConfiguration configuration); } diff --git a/core/src/main/java/com/databasir/core/doc/factory/TableIndexDocFactory.java b/core/src/main/java/com/databasir/core/doc/factory/TableIndexDocFactory.java index 5046801..42ba249 100644 --- a/core/src/main/java/com/databasir/core/doc/factory/TableIndexDocFactory.java +++ b/core/src/main/java/com/databasir/core/doc/factory/TableIndexDocFactory.java @@ -2,10 +2,11 @@ package com.databasir.core.doc.factory; import com.databasir.core.doc.model.IndexDoc; +import java.sql.DatabaseMetaData; import java.util.List; public interface TableIndexDocFactory extends Sortable { - List create(TableDocCreateContext context); + List create(String tableName, DatabaseMetaData metaData, DatabaseDocConfiguration configuration); } diff --git a/core/src/main/java/com/databasir/core/doc/factory/TableTriggerDocFactory.java b/core/src/main/java/com/databasir/core/doc/factory/TableTriggerDocFactory.java index 84b8445..e4caeb9 100644 --- a/core/src/main/java/com/databasir/core/doc/factory/TableTriggerDocFactory.java +++ b/core/src/main/java/com/databasir/core/doc/factory/TableTriggerDocFactory.java @@ -2,9 +2,10 @@ package com.databasir.core.doc.factory; import com.databasir.core.doc.model.TriggerDoc; +import java.sql.DatabaseMetaData; import java.util.List; public interface TableTriggerDocFactory extends Sortable { - List create(TableDocCreateContext context); + List create(String tableName, DatabaseMetaData metaData, DatabaseDocConfiguration configuration); } diff --git a/core/src/main/java/com/databasir/core/doc/factory/extension/MysqlTableTriggerDocFactory.java b/core/src/main/java/com/databasir/core/doc/factory/extension/MysqlTableTriggerDocFactory.java index a273163..bef512e 100644 --- a/core/src/main/java/com/databasir/core/doc/factory/extension/MysqlTableTriggerDocFactory.java +++ b/core/src/main/java/com/databasir/core/doc/factory/extension/MysqlTableTriggerDocFactory.java @@ -1,16 +1,20 @@ package com.databasir.core.doc.factory.extension; -import com.databasir.core.doc.factory.TableDocCreateContext; +import com.databasir.core.doc.factory.DatabaseDocConfiguration; import com.databasir.core.doc.factory.TableTriggerDocFactory; import com.databasir.core.doc.model.TriggerDoc; +import java.sql.DatabaseMetaData; +import java.util.Collections; import java.util.List; public class MysqlTableTriggerDocFactory implements TableTriggerDocFactory { @Override - public List create(TableDocCreateContext context) { - return null; + public List create(String tableName, + DatabaseMetaData metaData, + DatabaseDocConfiguration configuration) { + return Collections.emptyList(); } } diff --git a/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcDatabaseDocFactory.java b/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcDatabaseDocFactory.java index 0758c73..d092c3a 100644 --- a/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcDatabaseDocFactory.java +++ b/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcDatabaseDocFactory.java @@ -1,24 +1,45 @@ package com.databasir.core.doc.factory.jdbc; +import com.databasir.core.doc.factory.DatabaseDocConfiguration; import com.databasir.core.doc.factory.*; import com.databasir.core.doc.model.DatabaseDoc; +import com.databasir.core.doc.model.TableDoc; -import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; import java.util.Optional; public class JdbcDatabaseDocFactory implements DatabaseDocFactory { - private TableDocFactory tableDocFactory; - - private TableColumnDocFactory tableColumnDocFactory; - - private TableTriggerDocFactory tableTriggerDocFactory; - - private TableIndexDocFactory tableIndexDocFactory; - - @Override - public Optional create(Connection connection, String database) { - return Optional.empty(); + public static JdbcDatabaseDocFactory of() { + return new JdbcDatabaseDocFactory(); } + @Override + public Optional create(DatabaseDocConfiguration configuration) { + try { + DatabaseMetaData metaData = configuration.getConnection().getMetaData(); + ResultSet catalogs = metaData.getCatalogs(); + while (catalogs.next()) { + String catalogName = catalogs.getString("TABLE_CAT"); + if (Objects.equals(configuration.getDatabaseName(), catalogName)) { + TableDocFactory tableDocFactory = configuration.getTableDocFactory(); + List tableDocs = tableDocFactory.create(metaData, configuration); + DatabaseDoc databaseDoc = DatabaseDoc.builder() + .productName(metaData.getDatabaseProductName()) + .productVersion(metaData.getDatabaseProductVersion()) + .databaseName(catalogName) + .tables(tableDocs) + .build(); + return Optional.of(databaseDoc); + } + } + return Optional.empty(); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + } } diff --git a/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableColumnDocFactory.java b/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableColumnDocFactory.java index 19635ef..ea1d70d 100644 --- a/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableColumnDocFactory.java +++ b/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableColumnDocFactory.java @@ -1,24 +1,72 @@ package com.databasir.core.doc.factory.jdbc; +import com.databasir.core.doc.factory.DatabaseDocConfiguration; import com.databasir.core.doc.factory.TableColumnDocFactory; -import com.databasir.core.doc.factory.TableDocCreateContext; import com.databasir.core.doc.model.ColumnDoc; import lombok.extern.slf4j.Slf4j; +import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; @Slf4j public class JdbcTableColumnDocFactory implements TableColumnDocFactory { + @Override - public List create(TableDocCreateContext context) { - + public List create(String tableName, + DatabaseMetaData metaData, + DatabaseDocConfiguration configuration) { try { - ResultSet indexResults = context.getDatabaseMetaData().getIndexInfo(context.getDatabase(), null, context.getTableName(), false, false); + return doCreate(tableName, metaData, configuration); } catch (SQLException e) { + throw new IllegalStateException(e); } - - return null; } + + private List doCreate(String tableName, + DatabaseMetaData metaData, + DatabaseDocConfiguration configuration) throws SQLException { + List columnDocs = new ArrayList<>(); + String database = configuration.getDatabaseName(); + ResultSet columnsResult; + try { + columnsResult = metaData.getColumns(database, null, tableName, null); + } catch (SQLException e) { + log.warn("warn: ignore " + database + "." + tableName); + return columnDocs; + } + while (columnsResult.next()) { + String columnName = columnsResult.getString("COLUMN_NAME"); + if (configuration.ignoredColumn(columnName)) { + if (log.isDebugEnabled()) { + log.warn("ignore column: " + columnName); + } + } else { + String columnType = columnsResult.getString("TYPE_NAME"); + Integer columnSize = columnsResult.getInt("COLUMN_SIZE"); + Integer decimalDigits = columnsResult.getInt("DECIMAL_DIGITS"); + String defaultValue = columnsResult.getString("COLUMN_DEF"); + boolean isNullable = Objects.equals("YES", columnsResult.getString("IS_NULLABLE")); + boolean isAutoIncrement = Objects.equals("YES", columnsResult.getString("IS_AUTOINCREMENT")); + String columnComment = columnsResult.getString("REMARKS"); + ColumnDoc columnDoc = ColumnDoc.builder() + .name(columnName) + .type(columnType) + .size(columnSize) + .decimalDigits(decimalDigits) + .isNullable(isNullable) + .isAutoIncrement(isAutoIncrement) + .comment(columnComment) + .defaultValue(defaultValue) + .build(); + columnDocs.add(columnDoc); + } + + } + return columnDocs; + } + } diff --git a/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableDocFactory.java b/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableDocFactory.java index 16adc5a..214ac53 100644 --- a/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableDocFactory.java +++ b/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableDocFactory.java @@ -1,15 +1,59 @@ package com.databasir.core.doc.factory.jdbc; -import com.databasir.core.doc.factory.TableDocCreateContext; -import com.databasir.core.doc.factory.TableDocFactory; +import com.databasir.core.doc.factory.DatabaseDocConfiguration; +import com.databasir.core.doc.factory.*; import com.databasir.core.doc.model.TableDoc; +import lombok.extern.slf4j.Slf4j; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; +@Slf4j public class JdbcTableDocFactory implements TableDocFactory { @Override - public List create(TableDocCreateContext context) { - return null; + public List create(DatabaseMetaData metaData, + DatabaseDocConfiguration configuration) { + try { + return doCreateTableDoc(metaData, configuration); + } catch (SQLException e) { + throw new IllegalStateException(e); + } } + + private List doCreateTableDoc(DatabaseMetaData metaData, + DatabaseDocConfiguration configuration) throws SQLException { + List tableDocs = new ArrayList<>(); + if (metaData == null) { + return tableDocs; + } + + String databaseName = configuration.getDatabaseName(); + ResultSet tablesResult = metaData.getTables(databaseName, null, null, null); + while (tablesResult.next()) { + String tableName = tablesResult.getString("TABLE_NAME"); + if (configuration.ignoredTable(tableName)) { + if (log.isDebugEnabled()) { + log.debug("ignore table: " + tableName); + } + } else { + String tableType = tablesResult.getString("TABLE_TYPE"); + String tableComment = tablesResult.getString("REMARKS"); + TableDoc tableDoc = TableDoc.builder() + .tableName(tableName) + .tableType(tableType) + .tableComment(tableComment) + .columns(configuration.getTableColumnDocFactory().create(tableName, metaData, configuration)) + .indexes(configuration.getTableIndexDocFactory().create(tableName, metaData, configuration)) + .triggers(configuration.getTableTriggerDocFactory().create(tableName, metaData, configuration)) + .build(); + tableDocs.add(tableDoc); + } + } + return tableDocs; + } + } diff --git a/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableIndexDocFactory.java b/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableIndexDocFactory.java index 1aef6c5..d214e8a 100644 --- a/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableIndexDocFactory.java +++ b/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableIndexDocFactory.java @@ -1,14 +1,64 @@ package com.databasir.core.doc.factory.jdbc; -import com.databasir.core.doc.factory.TableDocCreateContext; +import com.databasir.core.doc.factory.DatabaseDocConfiguration; import com.databasir.core.doc.factory.TableIndexDocFactory; import com.databasir.core.doc.model.IndexDoc; +import lombok.extern.slf4j.Slf4j; -import java.util.List; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; +@Slf4j public class JdbcTableIndexDocFactory implements TableIndexDocFactory { + @Override - public List create(TableDocCreateContext context) { - return null; + public List create(String tableName, DatabaseMetaData metaData, DatabaseDocConfiguration configuration) { + try { + return doCreateIndexDocs(tableName, metaData, configuration); + } catch (SQLException e) { + throw new IllegalStateException(e); + } } + + private List doCreateIndexDocs(String tableName, + DatabaseMetaData metaData, + DatabaseDocConfiguration configuration) + throws SQLException { + List indexDocs = new ArrayList<>(); + String databaseName = configuration.getDatabaseName(); + if (databaseName == null || tableName == null || metaData == null) { + return indexDocs; + } + ResultSet indexResults; + try { + indexResults = metaData.getIndexInfo(databaseName, null, tableName, false, false); + } catch (SQLException e) { + log.warn("warn: ignore " + databaseName + "." + tableName); + return indexDocs; + } + + Map docsGroupByName = new HashMap<>(); + while (indexResults.next()) { + Boolean nonUnique = indexResults.getBoolean("NON_UNIQUE"); + String indexName = indexResults.getString("INDEX_NAME"); + String columnName = indexResults.getString("COLUMN_NAME"); + if (docsGroupByName.containsKey(indexName)) { + docsGroupByName.get(indexName).getColumnNames().add(columnName); + } else { + List columns = new ArrayList<>(); + columns.add(columnName); + IndexDoc columnDoc = IndexDoc.builder() + .indexName(indexName) + .columnNames(columns) + .isPrimaryKey(Objects.equals("PRIMARY", indexName)) + .isUniqueKey(Objects.equals(nonUnique, false)) + .build(); + docsGroupByName.put(indexName, columnDoc); + } + } + return new ArrayList<>(docsGroupByName.values()); + } + } diff --git a/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableTriggerDocFactory.java b/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableTriggerDocFactory.java index 74e1283..1c10674 100644 --- a/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableTriggerDocFactory.java +++ b/core/src/main/java/com/databasir/core/doc/factory/jdbc/JdbcTableTriggerDocFactory.java @@ -1,18 +1,18 @@ package com.databasir.core.doc.factory.jdbc; -import com.databasir.core.doc.factory.TableDocCreateContext; +import com.databasir.core.doc.factory.DatabaseDocConfiguration; import com.databasir.core.doc.factory.TableTriggerDocFactory; import com.databasir.core.doc.model.TriggerDoc; +import java.sql.DatabaseMetaData; import java.util.Collections; import java.util.List; public class JdbcTableTriggerDocFactory implements TableTriggerDocFactory { @Override - public List create(TableDocCreateContext context) { + public List create(String tableName, DatabaseMetaData metaData, DatabaseDocConfiguration configuration) { // note: jdbc not support get triggers return Collections.emptyList(); } - } diff --git a/core/src/main/java/com/databasir/core/doc/model/ColumnDoc.java b/core/src/main/java/com/databasir/core/doc/model/ColumnDoc.java index 4c18e41..96df982 100644 --- a/core/src/main/java/com/databasir/core/doc/model/ColumnDoc.java +++ b/core/src/main/java/com/databasir/core/doc/model/ColumnDoc.java @@ -1,4 +1,26 @@ package com.databasir.core.doc.model; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder public class ColumnDoc { -} + + private String name; + + private String comment; + + private String type; + + private String defaultValue; + + private Integer size; + + private Integer decimalDigits; + + private Boolean isNullable; + + private Boolean isAutoIncrement; + +} \ No newline at end of file diff --git a/core/src/main/java/com/databasir/core/doc/model/DatabaseDoc.java b/core/src/main/java/com/databasir/core/doc/model/DatabaseDoc.java index f33b8a4..00a029b 100644 --- a/core/src/main/java/com/databasir/core/doc/model/DatabaseDoc.java +++ b/core/src/main/java/com/databasir/core/doc/model/DatabaseDoc.java @@ -1,7 +1,28 @@ package com.databasir.core.doc.model; +import lombok.Builder; import lombok.Data; +import java.util.Collections; +import java.util.List; + @Data +@Builder public class DatabaseDoc { + + private String productName; + + private String productVersion; + + private String driverName; + + private String driverVersion; + + private String databaseName; + + private String remark; + + @Builder.Default + private List tables = Collections.emptyList(); + } diff --git a/core/src/main/java/com/databasir/core/doc/model/IndexDoc.java b/core/src/main/java/com/databasir/core/doc/model/IndexDoc.java index c727242..5097234 100644 --- a/core/src/main/java/com/databasir/core/doc/model/IndexDoc.java +++ b/core/src/main/java/com/databasir/core/doc/model/IndexDoc.java @@ -1,4 +1,21 @@ package com.databasir.core.doc.model; +import lombok.Builder; +import lombok.Data; + +import java.util.Collections; +import java.util.List; + +@Data +@Builder public class IndexDoc { + + private String indexName; + + @Builder.Default + private List columnNames = Collections.emptyList(); + + private Boolean isPrimaryKey; + + private Boolean isUniqueKey; } diff --git a/core/src/main/java/com/databasir/core/doc/model/TableDoc.java b/core/src/main/java/com/databasir/core/doc/model/TableDoc.java index fdb271b..58c8617 100644 --- a/core/src/main/java/com/databasir/core/doc/model/TableDoc.java +++ b/core/src/main/java/com/databasir/core/doc/model/TableDoc.java @@ -1,4 +1,29 @@ package com.databasir.core.doc.model; +import lombok.Builder; +import lombok.Data; + +import java.util.Collections; +import java.util.List; + +@Data +@Builder public class TableDoc { + + private String tableName; + + private String tableType; + + private String tableComment; + + @Builder.Default + private List columns = Collections.emptyList(); + + @Builder.Default + private List triggers = Collections.emptyList(); + + @Builder.Default + private List indexes = Collections.emptyList(); + + private String remark; } diff --git a/core/src/main/java/com/databasir/core/doc/model/TriggerDoc.java b/core/src/main/java/com/databasir/core/doc/model/TriggerDoc.java index c03f10e..82a987c 100644 --- a/core/src/main/java/com/databasir/core/doc/model/TriggerDoc.java +++ b/core/src/main/java/com/databasir/core/doc/model/TriggerDoc.java @@ -1,4 +1,24 @@ package com.databasir.core.doc.model; +import lombok.Builder; +import lombok.Data; + +/** + * now: only support mysql, postgresql. + */ +@Data +@Builder public class TriggerDoc { + + private String name; + + /** + * example 1: BEFORE UPDATE + * example 2: AFTER INSERT + */ + private String timing; + + private String statement; + + private String createAt; } diff --git a/core/src/main/java/com/databasir/core/doc/render/ColumnValueConverter.java b/core/src/main/java/com/databasir/core/doc/render/ColumnValueConverter.java deleted file mode 100644 index 4dbafab..0000000 --- a/core/src/main/java/com/databasir/core/doc/render/ColumnValueConverter.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.databasir.core.doc.render; - -import java.util.Objects; - -public interface ColumnValueConverter { - - default String convertDataType(String originType) { - return originType; - } - - default String convertIsNotNull(Boolean isNotNull) { - return Objects.equals(isNotNull, true) ? "YES" : ""; - } - - default String convertIsAutoIncrement(Boolean isAutoIncrement) { - return Objects.equals(isAutoIncrement, true) ? "YES" : ""; - } - -} diff --git a/core/src/main/java/com/databasir/core/doc/render/DefaultColumnValueConverter.java b/core/src/main/java/com/databasir/core/doc/render/DefaultColumnValueConverter.java deleted file mode 100644 index 1a3b34c..0000000 --- a/core/src/main/java/com/databasir/core/doc/render/DefaultColumnValueConverter.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.databasir.core.doc.render; - -public class DefaultColumnValueConverter implements ColumnValueConverter { -} diff --git a/core/src/main/java/com/databasir/core/doc/render/Render.java b/core/src/main/java/com/databasir/core/doc/render/Render.java index 379e006..a1c7279 100644 --- a/core/src/main/java/com/databasir/core/doc/render/Render.java +++ b/core/src/main/java/com/databasir/core/doc/render/Render.java @@ -1,11 +1,16 @@ package com.databasir.core.doc.render; import com.databasir.core.doc.model.DatabaseDoc; +import com.databasir.core.doc.render.markdown.MarkdownRender; +import java.io.IOException; import java.io.OutputStream; public interface Render { - void rendering(DatabaseDoc doc, OutputStream outputStream); + void rendering(DatabaseDoc doc, OutputStream outputStream) throws IOException; + static Render markdownRender(RenderConfiguration configuration) { + return MarkdownRender.of(configuration); + } } diff --git a/core/src/main/java/com/databasir/core/doc/render/RenderConfiguration.java b/core/src/main/java/com/databasir/core/doc/render/RenderConfiguration.java index 9b8c1ba..de0482f 100644 --- a/core/src/main/java/com/databasir/core/doc/render/RenderConfiguration.java +++ b/core/src/main/java/com/databasir/core/doc/render/RenderConfiguration.java @@ -1,5 +1,13 @@ package com.databasir.core.doc.render; +import com.databasir.core.doc.model.ColumnDoc; +import com.databasir.core.doc.model.IndexDoc; +import lombok.Data; + +import java.util.LinkedHashMap; +import java.util.function.Function; + +@Data public class RenderConfiguration { private Boolean renderTables = true; @@ -8,11 +16,47 @@ public class RenderConfiguration { private Boolean renderIndexes = true; - private Boolean renderViews = false; - private Boolean renderTriggers = false; - private Boolean renderProducers = false; + private LinkedHashMap> columnTitleAndValueMapping = columnTitleAndValueMapping(); - private ColumnValueConverter columnValueConverter = new DefaultColumnValueConverter(); + private LinkedHashMap> indexTitleAndValueMapping = indexTitleAndValueMapping(); + + protected LinkedHashMap> columnTitleAndValueMapping() { + LinkedHashMap> mapping = new LinkedHashMap<>(); + mapping.put("Name", ColumnDoc::getName); + mapping.put("Type", column -> { + String type; + if (column.getDecimalDigits() == null || column.getDecimalDigits().equals(0)) { + type = column.getType() + + "(" + column.getSize().toString() + ")"; + } else { + type = column.getType() + + "(" + column.getSize().toString() + ", " + column.getDecimalDigits().toString() + ")"; + } + return type; + }); + mapping.put("Not Null", column -> column.getIsNullable() ? "" : "YES"); + mapping.put("Auto Increment", column -> column.getIsAutoIncrement() ? "YES" : ""); + mapping.put("Default", column -> { + if (column.getDefaultValue() == null) { + return ""; + } + if (column.getDefaultValue().trim().equals("")) { + return "'" + column.getDefaultValue() + "'"; + } + return column.getDefaultValue(); + }); + mapping.put("Comment", ColumnDoc::getComment); + return mapping; + } + + protected LinkedHashMap> indexTitleAndValueMapping() { + LinkedHashMap> mapping = new LinkedHashMap<>(); + mapping.put("Name", IndexDoc::getIndexName); + mapping.put("IsPrimary", index -> index.getIsPrimaryKey() ? "YES" : ""); + mapping.put("IsUnique", index -> index.getIsUniqueKey() ? "YES" : ""); + mapping.put("Columns", index -> String.join(", ", index.getColumnNames())); + return mapping; + } } diff --git a/core/src/main/java/com/databasir/core/doc/render/Renders.java b/core/src/main/java/com/databasir/core/doc/render/Renders.java deleted file mode 100644 index a1ffbe2..0000000 --- a/core/src/main/java/com/databasir/core/doc/render/Renders.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.databasir.core.doc.render; - -import com.databasir.core.doc.model.DatabaseDoc; - -import java.io.OutputStream; - -public class Renders { - - private Render markdownRender = null; - - public void render(DatabaseDoc doc, - OutputStream outputStream, - RenderConfiguration config) { - markdownRender.rendering(doc, outputStream); - } -} diff --git a/core/src/main/java/com/databasir/core/doc/render/markdown/MarkdownRender.java b/core/src/main/java/com/databasir/core/doc/render/markdown/MarkdownRender.java index 040db8e..cd12e1b 100644 --- a/core/src/main/java/com/databasir/core/doc/render/markdown/MarkdownRender.java +++ b/core/src/main/java/com/databasir/core/doc/render/markdown/MarkdownRender.java @@ -1,4 +1,89 @@ package com.databasir.core.doc.render.markdown; -public class MarkdownRender { +import com.databasir.core.doc.model.DatabaseDoc; +import com.databasir.core.doc.model.TableDoc; +import com.databasir.core.doc.render.Render; +import com.databasir.core.doc.render.RenderConfiguration; +import lombok.Getter; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +public class MarkdownRender implements Render { + + @Getter + private final RenderConfiguration config; + + protected MarkdownRender(RenderConfiguration config) { + this.config = config; + } + + public static MarkdownRender of(RenderConfiguration config) { + return new MarkdownRender(config); + } + + @Override + public void rendering(DatabaseDoc doc, + OutputStream outputStream) throws IOException { + MarkdownBuilder contentBuilder = MarkdownBuilder.builder(); + contentBuilder.primaryTitle(doc.getDatabaseName()); + if (config.getRenderTables()) { + for (TableDoc table : doc.getTables()) { + buildTableName(contentBuilder, table); + if (config.getRenderColumns()) { + buildColumns(contentBuilder, table); + } + if (config.getRenderIndexes()) { + buildIndexes(contentBuilder, table); + } + } + } + outputStream.write(contentBuilder.build().getBytes(StandardCharsets.UTF_8)); + } + + private void buildTableName(MarkdownBuilder contentBuilder, TableDoc table) { + String tableName; + if (table.getTableComment() == null || table.getTableComment().trim().isEmpty()) { + tableName = table.getTableName(); + } else { + tableName = table.getTableName() + "(" + table.getTableComment() + ")"; + } + contentBuilder.secondTitle(tableName); + } + + private void buildColumns(MarkdownBuilder contentBuilder, TableDoc table) { + contentBuilder.unorderedList(Collections.singletonList("columns")); + List> allColumnRows = table.getColumns() + .stream() + .map(column -> config.getColumnTitleAndValueMapping() + .values() + .stream() + .map(mapping -> mapping.apply(column)) + .collect(Collectors.toList())) + .collect(Collectors.toList()); + contentBuilder.table(tableTitles(), allColumnRows); + } + + private void buildIndexes(MarkdownBuilder contentBuilder, TableDoc table) { + contentBuilder.unorderedList(Collections.singletonList("indexes")); + List> allIndexRows = table.getIndexes().stream() + .map(index -> config.getIndexTitleAndValueMapping() + .values() + .stream() + .map(mapping -> mapping.apply(index)) + .collect(Collectors.toList())) + .collect(Collectors.toList()); + contentBuilder.table(indexTitles(), allIndexRows); + } + + private List tableTitles() { + return new ArrayList<>(config.getColumnTitleAndValueMapping().keySet()); + } + + private List indexTitles() { + return new ArrayList<>(config.getIndexTitleAndValueMapping().keySet()); + } } diff --git a/core/src/test/java/App.java b/core/src/test/java/App.java new file mode 100644 index 0000000..3d13cc3 --- /dev/null +++ b/core/src/test/java/App.java @@ -0,0 +1,39 @@ +import com.databasir.core.doc.factory.DatabaseDocConfiguration; +import com.databasir.core.doc.factory.jdbc.JdbcDatabaseDocFactory; +import com.databasir.core.doc.model.DatabaseDoc; +import com.databasir.core.doc.render.Render; +import com.databasir.core.doc.render.RenderConfiguration; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Properties; + +public class App { + public static void main(String[] args) throws SQLException, ClassNotFoundException { + // get database connection + Class.forName("com.mysql.cj.jdbc.Driver"); + Properties info = new Properties(); + info.put("user", "root"); + info.put("password", "123456"); + // this config is used by mysql + info.put("useInformationSchema", "true"); + String url = "jdbc:mysql://localhost:3306/patient?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true"; + var connection = DriverManager.getConnection(url, info); + + // generate doc model + var config = DatabaseDocConfiguration.builder() + .databaseName("patient") + .connection(connection) + .build(); + DatabaseDoc doc = JdbcDatabaseDocFactory.of().create(config).orElseThrow(); + + // render as markdown + try (FileOutputStream out = new FileOutputStream("doc.md")) { + Render.markdownRender(new RenderConfiguration()).rendering(doc, out); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } +}