43 changed files with 636 additions and 477 deletions
			
			
		| @ -0,0 +1,46 @@ | |||||
|  | package com.databasir.core; | ||||
|  | 
 | ||||
|  | import com.databasir.core.meta.pojo.DatabaseMeta; | ||||
|  | import com.databasir.core.meta.repository.condition.Condition; | ||||
|  | import com.databasir.core.render.Render; | ||||
|  | import com.databasir.core.render.RenderConfig; | ||||
|  | import lombok.Getter; | ||||
|  | import lombok.RequiredArgsConstructor; | ||||
|  | 
 | ||||
|  | import java.io.IOException; | ||||
|  | import java.io.OutputStream; | ||||
|  | import java.sql.Connection; | ||||
|  | import java.util.Optional; | ||||
|  | 
 | ||||
|  | @RequiredArgsConstructor | ||||
|  | @Getter | ||||
|  | public class Databasir { | ||||
|  | 
 | ||||
|  |     private final DatabasirConfig config; | ||||
|  | 
 | ||||
|  |     public Optional<DatabaseMeta> get(Connection connection, String databaseName) { | ||||
|  |         Condition condition = Condition.builder() | ||||
|  |                 .databaseName(databaseName) | ||||
|  |                 .ignoreTableNameRegex(config.getIgnoreTableNameRegex()) | ||||
|  |                 .ignoreTableColumnNameRegex(config.getIgnoreTableColumnNameRegex()) | ||||
|  |                 .build(); | ||||
|  |         return config.getDatabaseMetaRepository().select(connection, condition); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     public void renderAsMarkdown(DatabaseMeta meta, OutputStream out) throws IOException { | ||||
|  |         renderAsMarkdown(new RenderConfig(), meta, out); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     public void renderAsMarkdown(RenderConfig config, DatabaseMeta meta, OutputStream stream) throws IOException { | ||||
|  |         Render.markdownRender(config).rendering(meta, stream); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     public static Databasir of() { | ||||
|  |         return of(new DatabasirConfig()); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     public static Databasir of(DatabasirConfig config) { | ||||
|  |         return new Databasir(config); | ||||
|  |     } | ||||
|  | 
 | ||||
|  | } | ||||
| @ -0,0 +1,39 @@ | |||||
|  | package com.databasir.core; | ||||
|  | 
 | ||||
|  | import com.databasir.core.meta.repository.*; | ||||
|  | import com.databasir.core.meta.repository.impl.jdbc.*; | ||||
|  | import lombok.Getter; | ||||
|  | import lombok.Setter; | ||||
|  | 
 | ||||
|  | import java.util.Collection; | ||||
|  | import java.util.HashSet; | ||||
|  | 
 | ||||
|  | @Getter | ||||
|  | @Setter | ||||
|  | public class DatabasirConfig { | ||||
|  | 
 | ||||
|  |     private IndexMetaRepository indexMetaRepository = new JdbcIndexMetaRepository(); | ||||
|  | 
 | ||||
|  |     private TriggerMetaRepository triggerMetaRepository = new JdbcTriggerMetaRepository(); | ||||
|  | 
 | ||||
|  |     private ColumnMetaRepository columnMetaRepository = new JdbcColumnMetaRepository(); | ||||
|  | 
 | ||||
|  |     private TableMetaRepository tableMetaRepository = | ||||
|  |             new JdbcTableMetaRepository(columnMetaRepository, indexMetaRepository, triggerMetaRepository); | ||||
|  | 
 | ||||
|  |     private DatabaseMetaRepository databaseMetaRepository = new JdbcDatabaseMetaRepository(tableMetaRepository); | ||||
|  | 
 | ||||
|  |     private Collection<String> ignoreTableNameRegex = new HashSet<>(); | ||||
|  | 
 | ||||
|  |     private Collection<String> ignoreTableColumnNameRegex = new HashSet<>(); | ||||
|  | 
 | ||||
|  |     public DatabasirConfig ignoreTable(String tableNameRegex) { | ||||
|  |         ignoreTableNameRegex.add(tableNameRegex); | ||||
|  |         return this; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     public DatabasirConfig ignoreColumn(String columnNameRegex) { | ||||
|  |         ignoreTableColumnNameRegex.add(columnNameRegex); | ||||
|  |         return this; | ||||
|  |     } | ||||
|  | } | ||||
| @ -1,49 +0,0 @@ | |||||
| package com.databasir.core.doc.factory; |  | ||||
| 
 |  | ||||
| import com.databasir.core.doc.factory.jdbc.*; |  | ||||
| import lombok.Builder; |  | ||||
| import lombok.Getter; |  | ||||
| import lombok.NonNull; |  | ||||
| 
 |  | ||||
| import java.sql.Connection; |  | ||||
| import java.util.Collections; |  | ||||
| import java.util.List; |  | ||||
| import java.util.regex.Pattern; |  | ||||
| 
 |  | ||||
| @Builder |  | ||||
| @Getter |  | ||||
| public class DatabaseDocConfig { |  | ||||
| 
 |  | ||||
|     @NonNull |  | ||||
|     private String databaseName; |  | ||||
| 
 |  | ||||
|     @NonNull |  | ||||
|     private Connection connection; |  | ||||
| 
 |  | ||||
|     @Builder.Default |  | ||||
|     private List<String> ignoreTableRegexes = Collections.emptyList(); |  | ||||
| 
 |  | ||||
|     @Builder.Default |  | ||||
|     private List<String> ignoreColumnRegexes = Collections.emptyList(); |  | ||||
| 
 |  | ||||
|     @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 tableIsIgnored(String tableName) { |  | ||||
|         return ignoreTableRegexes.stream().anyMatch(regex -> Pattern.matches(regex, tableName)); |  | ||||
|     } |  | ||||
| 
 |  | ||||
|     public boolean columnIsIgnored(String column) { |  | ||||
|         return ignoreColumnRegexes.stream().anyMatch(regex -> Pattern.matches(regex, column)); |  | ||||
|     } |  | ||||
| 
 |  | ||||
| } |  | ||||
| @ -1,11 +0,0 @@ | |||||
| package com.databasir.core.doc.factory; |  | ||||
| 
 |  | ||||
| import com.databasir.core.doc.model.DatabaseDoc; |  | ||||
| 
 |  | ||||
| import java.util.Optional; |  | ||||
| 
 |  | ||||
| public interface DatabaseDocFactory extends Sortable<DatabaseDocFactory> { |  | ||||
| 
 |  | ||||
|     Optional<DatabaseDoc> create(DatabaseDocConfig configuration); |  | ||||
| 
 |  | ||||
| } |  | ||||
| @ -1,16 +0,0 @@ | |||||
| package com.databasir.core.doc.factory; |  | ||||
| 
 |  | ||||
| public interface Sortable<T extends Sortable<?>> extends Comparable<T> { |  | ||||
| 
 |  | ||||
|     /** |  | ||||
|      * @return priority, min -> max means low -> high |  | ||||
|      */ |  | ||||
|     default int order() { |  | ||||
|         return Integer.MIN_VALUE; |  | ||||
|     } |  | ||||
| 
 |  | ||||
|     @Override |  | ||||
|     default int compareTo(T o) { |  | ||||
|         return Integer.compare(this.order(), o.order()); |  | ||||
|     } |  | ||||
| } |  | ||||
| @ -1,12 +0,0 @@ | |||||
| 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<TableColumnDocFactory> { |  | ||||
| 
 |  | ||||
|     List<ColumnDoc> create(String tableName, DatabaseMetaData metaData, DatabaseDocConfig configuration); |  | ||||
| 
 |  | ||||
| } |  | ||||
| @ -1,12 +0,0 @@ | |||||
| 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<TableDocFactory> { |  | ||||
| 
 |  | ||||
|     List<TableDoc> create(DatabaseMetaData metaData, DatabaseDocConfig configuration); |  | ||||
| 
 |  | ||||
| } |  | ||||
| @ -1,12 +0,0 @@ | |||||
| 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<TableIndexDocFactory> { |  | ||||
| 
 |  | ||||
|     List<IndexDoc> create(String tableName, DatabaseMetaData metaData, DatabaseDocConfig configuration); |  | ||||
| 
 |  | ||||
| } |  | ||||
| @ -1,11 +0,0 @@ | |||||
| 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<TableTriggerDocFactory> { |  | ||||
| 
 |  | ||||
|     List<TriggerDoc> create(String tableName, DatabaseMetaData metaData, DatabaseDocConfig configuration); |  | ||||
| } |  | ||||
| @ -1,45 +0,0 @@ | |||||
| package com.databasir.core.doc.factory.jdbc; |  | ||||
| 
 |  | ||||
| import com.databasir.core.doc.factory.DatabaseDocConfig; |  | ||||
| import com.databasir.core.doc.factory.*; |  | ||||
| import com.databasir.core.doc.model.DatabaseDoc; |  | ||||
| import com.databasir.core.doc.model.TableDoc; |  | ||||
| 
 |  | ||||
| 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 { |  | ||||
| 
 |  | ||||
|     public static JdbcDatabaseDocFactory of() { |  | ||||
|         return new JdbcDatabaseDocFactory(); |  | ||||
|     } |  | ||||
| 
 |  | ||||
|     @Override |  | ||||
|     public Optional<DatabaseDoc> create(DatabaseDocConfig 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<TableDoc> 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); |  | ||||
|         } |  | ||||
|     } |  | ||||
| } |  | ||||
| @ -1,59 +0,0 @@ | |||||
| package com.databasir.core.doc.factory.jdbc; |  | ||||
| 
 |  | ||||
| import com.databasir.core.doc.factory.DatabaseDocConfig; |  | ||||
| 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<TableDoc> create(DatabaseMetaData metaData, |  | ||||
|                                  DatabaseDocConfig configuration) { |  | ||||
|         try { |  | ||||
|             return doCreateTableDoc(metaData, configuration); |  | ||||
|         } catch (SQLException e) { |  | ||||
|             throw new IllegalStateException(e); |  | ||||
|         } |  | ||||
|     } |  | ||||
| 
 |  | ||||
|     private List<TableDoc> doCreateTableDoc(DatabaseMetaData metaData, |  | ||||
|                                             DatabaseDocConfig configuration) throws SQLException { |  | ||||
|         List<TableDoc> 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.tableIsIgnored(tableName)) { |  | ||||
|                 if (log.isWarnEnabled()) { |  | ||||
|                     log.warn("ignore table: " + configuration.getDatabaseName() + "." + 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; |  | ||||
|     } |  | ||||
| 
 |  | ||||
| } |  | ||||
| @ -1,64 +0,0 @@ | |||||
| package com.databasir.core.doc.factory.jdbc; |  | ||||
| 
 |  | ||||
| import com.databasir.core.doc.factory.DatabaseDocConfig; |  | ||||
| import com.databasir.core.doc.factory.TableIndexDocFactory; |  | ||||
| import com.databasir.core.doc.model.IndexDoc; |  | ||||
| import lombok.extern.slf4j.Slf4j; |  | ||||
| 
 |  | ||||
| import java.sql.DatabaseMetaData; |  | ||||
| import java.sql.ResultSet; |  | ||||
| import java.sql.SQLException; |  | ||||
| import java.util.*; |  | ||||
| 
 |  | ||||
| @Slf4j |  | ||||
| public class JdbcTableIndexDocFactory implements TableIndexDocFactory { |  | ||||
| 
 |  | ||||
|     @Override |  | ||||
|     public List<IndexDoc> create(String tableName, DatabaseMetaData metaData, DatabaseDocConfig configuration) { |  | ||||
|         try { |  | ||||
|             return doCreateIndexDocs(tableName, metaData, configuration); |  | ||||
|         } catch (SQLException e) { |  | ||||
|             throw new IllegalStateException(e); |  | ||||
|         } |  | ||||
|     } |  | ||||
| 
 |  | ||||
|     private List<IndexDoc> doCreateIndexDocs(String tableName, |  | ||||
|                                              DatabaseMetaData metaData, |  | ||||
|                                              DatabaseDocConfig configuration) |  | ||||
|             throws SQLException { |  | ||||
|         List<IndexDoc> indexDocs = new ArrayList<>(); |  | ||||
|         String databaseName = configuration.getDatabaseName(); |  | ||||
|         if (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<String, IndexDoc> 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<String> 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()); |  | ||||
|     } |  | ||||
| 
 |  | ||||
| } |  | ||||
| @ -1,18 +0,0 @@ | |||||
| package com.databasir.core.doc.factory.jdbc; |  | ||||
| 
 |  | ||||
| import com.databasir.core.doc.factory.DatabaseDocConfig; |  | ||||
| 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<TriggerDoc> create(String tableName, DatabaseMetaData metaData, DatabaseDocConfig configuration) { |  | ||||
|         // note: jdbc not support get triggers
 |  | ||||
|         return Collections.emptyList(); |  | ||||
|     } |  | ||||
| } |  | ||||
| @ -1,16 +0,0 @@ | |||||
| 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) throws IOException; |  | ||||
| 
 |  | ||||
|     static Render markdownRender(RenderConfig configuration) { |  | ||||
|         return MarkdownRender.of(configuration); |  | ||||
|     } |  | ||||
| } |  | ||||
| @ -1,11 +1,11 @@ | |||||
| package com.databasir.core.doc.model; | package com.databasir.core.meta.pojo; | ||||
| 
 | 
 | ||||
| import lombok.Builder; | import lombok.Builder; | ||||
| import lombok.Data; | import lombok.Data; | ||||
| 
 | 
 | ||||
| @Data | @Data | ||||
| @Builder | @Builder | ||||
| public class ColumnDoc { | public class ColumnMeta { | ||||
| 
 | 
 | ||||
|     private String name; |     private String name; | ||||
| 
 | 
 | ||||
| @ -0,0 +1,80 @@ | |||||
|  | package com.databasir.core.meta.provider; | ||||
|  | 
 | ||||
|  | import java.util.Optional; | ||||
|  | 
 | ||||
|  | /** | ||||
|  |  * TODO use to extension repository | ||||
|  |  */ | ||||
|  | public interface SqlProvider { | ||||
|  | 
 | ||||
|  |     Default DEFAULT = new Default(); | ||||
|  | 
 | ||||
|  |     /** | ||||
|  |      * <p> | ||||
|  |      * generate sql to select database information, should return the follow columns | ||||
|  |      * </p> | ||||
|  |      * | ||||
|  |      * <table> | ||||
|  |      *     <tr> | ||||
|  |      *         <th> column name </th> | ||||
|  |      *         <th> column type </th> | ||||
|  |      *         <th> description </th> | ||||
|  |      *         <th> nullable </th> | ||||
|  |      *     </tr> | ||||
|  |      *     <tr> | ||||
|  |      *         <td> TABLE_CAT </td> | ||||
|  |      *         <td> String </td> | ||||
|  |      *         <td> catalog name </td> | ||||
|  |      *         <td> NO </td> | ||||
|  |      *     </tr> | ||||
|  |      * </table> | ||||
|  |      * <br> | ||||
|  |      * | ||||
|  |      * @param databaseName | ||||
|  |      * @return | ||||
|  |      */ | ||||
|  |     default Optional<String> databaseMetaSql(String databaseName) { | ||||
|  |         return Optional.empty(); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     /** | ||||
|  |      * generate sql to select table information, should return the follow columns | ||||
|  |      * <table> | ||||
|  |      *     <tr> | ||||
|  |      *         <th> column name </th> | ||||
|  |      *         <th> column type </th> | ||||
|  |      *         <th> description </th> | ||||
|  |      *         <th> nullable </th> | ||||
|  |      *     </tr> | ||||
|  |      *     <tr> | ||||
|  |      *         <td> TABLE_CAT </td> | ||||
|  |      *         <td> String </td> | ||||
|  |      *         <td> catalog name </td> | ||||
|  |      *         <td> NO </td> | ||||
|  |      *     </tr> | ||||
|  |      * </table> | ||||
|  |      * | ||||
|  |      * @param databaseName | ||||
|  |      * @param tableName | ||||
|  |      * @return | ||||
|  |      */ | ||||
|  |     default Optional<String> tableMetaSql(String databaseName, String tableName) { | ||||
|  |         return Optional.empty(); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     default Optional<String> tableColumnMetaSql(String databaseName, String tableName) { | ||||
|  |         return Optional.empty(); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     default Optional<String> tableIndexMetaSql(String databaseName, String tableName) { | ||||
|  |         return Optional.empty(); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     default Optional<String> tableTriggerMetaSql(String databaseName, String tableName) { | ||||
|  |         return Optional.empty(); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     class Default implements SqlProvider { | ||||
|  | 
 | ||||
|  |     } | ||||
|  | } | ||||
| @ -0,0 +1,13 @@ | |||||
|  | package com.databasir.core.meta.repository; | ||||
|  | 
 | ||||
|  | import com.databasir.core.meta.pojo.ColumnMeta; | ||||
|  | import com.databasir.core.meta.repository.condition.TableCondition; | ||||
|  | 
 | ||||
|  | import java.sql.Connection; | ||||
|  | import java.util.List; | ||||
|  | 
 | ||||
|  | public interface ColumnMetaRepository { | ||||
|  | 
 | ||||
|  |     List<ColumnMeta> selectColumns(Connection connection, TableCondition condition); | ||||
|  | 
 | ||||
|  | } | ||||
| @ -0,0 +1,13 @@ | |||||
|  | package com.databasir.core.meta.repository; | ||||
|  | 
 | ||||
|  | import com.databasir.core.meta.pojo.DatabaseMeta; | ||||
|  | import com.databasir.core.meta.repository.condition.Condition; | ||||
|  | 
 | ||||
|  | import java.sql.Connection; | ||||
|  | import java.util.Optional; | ||||
|  | 
 | ||||
|  | public interface DatabaseMetaRepository { | ||||
|  | 
 | ||||
|  |     Optional<DatabaseMeta> select(Connection connection, Condition condition); | ||||
|  | 
 | ||||
|  | } | ||||
| @ -0,0 +1,12 @@ | |||||
|  | package com.databasir.core.meta.repository; | ||||
|  | 
 | ||||
|  | import com.databasir.core.meta.pojo.IndexMeta; | ||||
|  | import com.databasir.core.meta.repository.condition.TableCondition; | ||||
|  | 
 | ||||
|  | import java.sql.Connection; | ||||
|  | import java.util.List; | ||||
|  | 
 | ||||
|  | public interface IndexMetaRepository { | ||||
|  | 
 | ||||
|  |     List<IndexMeta> selectIndexes(Connection connection, TableCondition condition); | ||||
|  | } | ||||
| @ -0,0 +1,13 @@ | |||||
|  | package com.databasir.core.meta.repository; | ||||
|  | 
 | ||||
|  | import com.databasir.core.meta.pojo.TableMeta; | ||||
|  | import com.databasir.core.meta.repository.condition.Condition; | ||||
|  | 
 | ||||
|  | import java.sql.Connection; | ||||
|  | import java.util.List; | ||||
|  | 
 | ||||
|  | public interface TableMetaRepository { | ||||
|  | 
 | ||||
|  |     List<TableMeta> selectTables(Connection connection, Condition condition); | ||||
|  | 
 | ||||
|  | } | ||||
| @ -0,0 +1,13 @@ | |||||
|  | package com.databasir.core.meta.repository; | ||||
|  | 
 | ||||
|  | import com.databasir.core.meta.pojo.TriggerMeta; | ||||
|  | import com.databasir.core.meta.repository.condition.TableCondition; | ||||
|  | 
 | ||||
|  | import java.sql.Connection; | ||||
|  | import java.util.List; | ||||
|  | 
 | ||||
|  | public interface TriggerMetaRepository { | ||||
|  | 
 | ||||
|  |     List<TriggerMeta> selectTriggers(Connection connection, TableCondition condition); | ||||
|  | 
 | ||||
|  | } | ||||
| @ -0,0 +1,32 @@ | |||||
|  | package com.databasir.core.meta.repository.condition; | ||||
|  | 
 | ||||
|  | import lombok.Builder; | ||||
|  | import lombok.Getter; | ||||
|  | import lombok.NonNull; | ||||
|  | import lombok.experimental.SuperBuilder; | ||||
|  | 
 | ||||
|  | import java.util.Collection; | ||||
|  | import java.util.Collections; | ||||
|  | import java.util.regex.Pattern; | ||||
|  | 
 | ||||
|  | @SuperBuilder | ||||
|  | @Getter | ||||
|  | public class Condition { | ||||
|  | 
 | ||||
|  |     @NonNull | ||||
|  |     private String databaseName; | ||||
|  | 
 | ||||
|  |     @Builder.Default | ||||
|  |     private Collection<String> ignoreTableNameRegex = Collections.emptyList(); | ||||
|  | 
 | ||||
|  |     @Builder.Default | ||||
|  |     private Collection<String> ignoreTableColumnNameRegex = Collections.emptyList(); | ||||
|  | 
 | ||||
|  |     public boolean tableIsIgnored(String tableName) { | ||||
|  |         return ignoreTableNameRegex.stream().anyMatch(regex -> Pattern.matches(regex, tableName)); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     public boolean columnIsIgnored(String column) { | ||||
|  |         return ignoreTableColumnNameRegex.stream().anyMatch(regex -> Pattern.matches(regex, column)); | ||||
|  |     } | ||||
|  | } | ||||
| @ -0,0 +1,24 @@ | |||||
|  | package com.databasir.core.meta.repository.condition; | ||||
|  | 
 | ||||
|  | import lombok.Getter; | ||||
|  | import lombok.NonNull; | ||||
|  | import lombok.experimental.SuperBuilder; | ||||
|  | 
 | ||||
|  | @SuperBuilder | ||||
|  | @Getter | ||||
|  | public class TableCondition extends Condition { | ||||
|  | 
 | ||||
|  |     @NonNull | ||||
|  |     private String tableName; | ||||
|  | 
 | ||||
|  |     public static TableCondition of(Condition condition, String tableName) { | ||||
|  |         return TableCondition.builder() | ||||
|  |                 .databaseName(condition.getDatabaseName()) | ||||
|  |                 .tableName(tableName) | ||||
|  |                 .ignoreTableNameRegex(condition.getIgnoreTableNameRegex()) | ||||
|  |                 .ignoreTableColumnNameRegex(condition.getIgnoreTableColumnNameRegex()) | ||||
|  |                 .build(); | ||||
|  |     } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | } | ||||
| @ -0,0 +1,47 @@ | |||||
|  | package com.databasir.core.meta.repository.impl.jdbc; | ||||
|  | 
 | ||||
|  | import com.databasir.core.meta.pojo.DatabaseMeta; | ||||
|  | import com.databasir.core.meta.pojo.TableMeta; | ||||
|  | import com.databasir.core.meta.repository.DatabaseMetaRepository; | ||||
|  | import com.databasir.core.meta.repository.TableMetaRepository; | ||||
|  | import com.databasir.core.meta.repository.condition.Condition; | ||||
|  | import lombok.RequiredArgsConstructor; | ||||
|  | 
 | ||||
|  | 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; | ||||
|  | 
 | ||||
|  | @RequiredArgsConstructor | ||||
|  | public class JdbcDatabaseMetaRepository implements DatabaseMetaRepository { | ||||
|  | 
 | ||||
|  |     private final TableMetaRepository tableMetaRepository; | ||||
|  | 
 | ||||
|  |     @Override | ||||
|  |     public Optional<DatabaseMeta> select(Connection connection, Condition condition) { | ||||
|  |         try { | ||||
|  |             DatabaseMetaData metaData = connection.getMetaData(); | ||||
|  |             ResultSet catalogs = metaData.getCatalogs(); | ||||
|  |             while (catalogs.next()) { | ||||
|  |                 String catalogName = catalogs.getString("TABLE_CAT"); | ||||
|  |                 if (Objects.equals(condition.getDatabaseName(), catalogName)) { | ||||
|  |                     List<TableMeta> tableDocs = tableMetaRepository.selectTables(connection, condition); | ||||
|  |                     DatabaseMeta meta = DatabaseMeta.builder() | ||||
|  |                             .productName(metaData.getDatabaseProductName()) | ||||
|  |                             .productVersion(metaData.getDatabaseProductVersion()) | ||||
|  |                             .databaseName(catalogName) | ||||
|  |                             .tables(tableDocs) | ||||
|  |                             .build(); | ||||
|  |                     return Optional.of(meta); | ||||
|  |                 } | ||||
|  |             } | ||||
|  |             return Optional.empty(); | ||||
|  |         } catch (SQLException e) { | ||||
|  |             throw new IllegalStateException(e); | ||||
|  |         } | ||||
|  |     } | ||||
|  | 
 | ||||
|  | } | ||||
| @ -0,0 +1,59 @@ | |||||
|  | package com.databasir.core.meta.repository.impl.jdbc; | ||||
|  | 
 | ||||
|  | import com.databasir.core.meta.pojo.IndexMeta; | ||||
|  | import com.databasir.core.meta.repository.IndexMetaRepository; | ||||
|  | import com.databasir.core.meta.repository.condition.TableCondition; | ||||
|  | import lombok.extern.slf4j.Slf4j; | ||||
|  | 
 | ||||
|  | import java.sql.Connection; | ||||
|  | import java.sql.ResultSet; | ||||
|  | import java.sql.SQLException; | ||||
|  | import java.util.*; | ||||
|  | 
 | ||||
|  | @Slf4j | ||||
|  | public class JdbcIndexMetaRepository implements IndexMetaRepository { | ||||
|  |     @Override | ||||
|  |     public List<IndexMeta> selectIndexes(Connection connection, TableCondition condition) { | ||||
|  |         try { | ||||
|  |             return doCreateIndexDocs(connection, condition); | ||||
|  |         } catch (SQLException e) { | ||||
|  |             throw new IllegalStateException(e); | ||||
|  |         } | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     private List<IndexMeta> doCreateIndexDocs(Connection connection, TableCondition condition) | ||||
|  |             throws SQLException { | ||||
|  |         String databaseName = condition.getDatabaseName(); | ||||
|  |         String tableName = condition.getTableName(); | ||||
|  |         List<IndexMeta> indexMetas = new ArrayList<>(); | ||||
|  |         ResultSet indexResults; | ||||
|  |         try { | ||||
|  |             indexResults = connection.getMetaData().getIndexInfo(databaseName, null, tableName, false, false); | ||||
|  |         } catch (SQLException e) { | ||||
|  |             log.warn("warn: ignore " + databaseName + "." + tableName); | ||||
|  |             return indexMetas; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         Map<String, IndexMeta> pojoGroupByName = new HashMap<>(); | ||||
|  |         while (indexResults.next()) { | ||||
|  |             Boolean nonUnique = indexResults.getBoolean("NON_UNIQUE"); | ||||
|  |             String indexName = indexResults.getString("INDEX_NAME"); | ||||
|  |             String columnName = indexResults.getString("COLUMN_NAME"); | ||||
|  |             if (pojoGroupByName.containsKey(indexName)) { | ||||
|  |                 pojoGroupByName.get(indexName).getColumnNames().add(columnName); | ||||
|  |             } else { | ||||
|  |                 List<String> columns = new ArrayList<>(); | ||||
|  |                 columns.add(columnName); | ||||
|  |                 IndexMeta indexMeta = IndexMeta.builder() | ||||
|  |                         .indexName(indexName) | ||||
|  |                         .columnNames(columns) | ||||
|  |                         .isPrimaryKey(Objects.equals("PRIMARY", indexName)) | ||||
|  |                         .isUniqueKey(Objects.equals(nonUnique, false)) | ||||
|  |                         .build(); | ||||
|  |                 pojoGroupByName.put(indexName, indexMeta); | ||||
|  |             } | ||||
|  |         } | ||||
|  |         return new ArrayList<>(pojoGroupByName.values()); | ||||
|  |     } | ||||
|  | 
 | ||||
|  | } | ||||
| @ -0,0 +1,66 @@ | |||||
|  | package com.databasir.core.meta.repository.impl.jdbc; | ||||
|  | 
 | ||||
|  | import com.databasir.core.meta.pojo.TableMeta; | ||||
|  | import com.databasir.core.meta.repository.ColumnMetaRepository; | ||||
|  | import com.databasir.core.meta.repository.IndexMetaRepository; | ||||
|  | import com.databasir.core.meta.repository.TableMetaRepository; | ||||
|  | import com.databasir.core.meta.repository.TriggerMetaRepository; | ||||
|  | import com.databasir.core.meta.repository.condition.Condition; | ||||
|  | import com.databasir.core.meta.repository.condition.TableCondition; | ||||
|  | import lombok.RequiredArgsConstructor; | ||||
|  | import lombok.extern.slf4j.Slf4j; | ||||
|  | 
 | ||||
|  | import java.sql.Connection; | ||||
|  | import java.sql.ResultSet; | ||||
|  | import java.sql.SQLException; | ||||
|  | import java.util.ArrayList; | ||||
|  | import java.util.List; | ||||
|  | 
 | ||||
|  | @RequiredArgsConstructor | ||||
|  | @Slf4j | ||||
|  | public class JdbcTableMetaRepository implements TableMetaRepository { | ||||
|  | 
 | ||||
|  |     private final ColumnMetaRepository columnMetaRepository; | ||||
|  | 
 | ||||
|  |     private final IndexMetaRepository indexMetaRepository; | ||||
|  | 
 | ||||
|  |     private final TriggerMetaRepository triggerMetaRepository; | ||||
|  | 
 | ||||
|  |     @Override | ||||
|  |     public List<TableMeta> selectTables(Connection connection, Condition condition) { | ||||
|  |         try { | ||||
|  |             return doSelect(connection, condition); | ||||
|  |         } catch (SQLException e) { | ||||
|  |             throw new IllegalStateException(e); | ||||
|  |         } | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     private List<TableMeta> doSelect(Connection connection, Condition condition) throws SQLException { | ||||
|  |         List<TableMeta> tableMetas = new ArrayList<>(); | ||||
|  |         String databaseName = condition.getDatabaseName(); | ||||
|  |         ResultSet tablesResult = connection.getMetaData().getTables(databaseName, null, null, null); | ||||
|  |         while (tablesResult.next()) { | ||||
|  |             String tableName = tablesResult.getString("TABLE_NAME"); | ||||
|  |             if (condition.tableIsIgnored(tableName)) { | ||||
|  |                 if (log.isWarnEnabled()) { | ||||
|  |                     log.warn("ignored table: " + databaseName + "." + tableName); | ||||
|  |                 } | ||||
|  |             } else { | ||||
|  |                 String tableType = tablesResult.getString("TABLE_TYPE"); | ||||
|  |                 String tableComment = tablesResult.getString("REMARKS"); | ||||
|  |                 TableCondition tableCondition = TableCondition.of(condition, tableName); | ||||
|  |                 TableMeta tableMeta = TableMeta.builder() | ||||
|  |                         .tableName(tableName) | ||||
|  |                         .tableType(tableType) | ||||
|  |                         .tableComment(tableComment) | ||||
|  |                         .columns(columnMetaRepository.selectColumns(connection, tableCondition)) | ||||
|  |                         .indexes(indexMetaRepository.selectIndexes(connection, tableCondition)) | ||||
|  |                         .triggers(triggerMetaRepository.selectTriggers(connection, tableCondition)) | ||||
|  |                         .build(); | ||||
|  |                 tableMetas.add(tableMeta); | ||||
|  |             } | ||||
|  |         } | ||||
|  |         return tableMetas; | ||||
|  |     } | ||||
|  | 
 | ||||
|  | } | ||||
| @ -0,0 +1,18 @@ | |||||
|  | package com.databasir.core.meta.repository.impl.jdbc; | ||||
|  | 
 | ||||
|  | import com.databasir.core.meta.pojo.TriggerMeta; | ||||
|  | import com.databasir.core.meta.repository.TriggerMetaRepository; | ||||
|  | import com.databasir.core.meta.repository.condition.TableCondition; | ||||
|  | 
 | ||||
|  | import java.sql.Connection; | ||||
|  | import java.util.Collections; | ||||
|  | import java.util.List; | ||||
|  | 
 | ||||
|  | public class JdbcTriggerMetaRepository implements TriggerMetaRepository { | ||||
|  | 
 | ||||
|  |     @Override | ||||
|  |     public List<TriggerMeta> selectTriggers(Connection connection, TableCondition condition) { | ||||
|  |         // note: jdbc not support get triggers
 | ||||
|  |         return Collections.emptyList(); | ||||
|  |     } | ||||
|  | } | ||||
| @ -0,0 +1,16 @@ | |||||
|  | package com.databasir.core.render; | ||||
|  | 
 | ||||
|  | import com.databasir.core.meta.pojo.DatabaseMeta; | ||||
|  | import com.databasir.core.render.markdown.MarkdownRender; | ||||
|  | 
 | ||||
|  | import java.io.IOException; | ||||
|  | import java.io.OutputStream; | ||||
|  | 
 | ||||
|  | public interface Render { | ||||
|  | 
 | ||||
|  |     void rendering(DatabaseMeta meta, OutputStream outputStream) throws IOException; | ||||
|  | 
 | ||||
|  |     static Render markdownRender(RenderConfig configuration) { | ||||
|  |         return MarkdownRender.of(configuration); | ||||
|  |     } | ||||
|  | } | ||||
| @ -1,4 +1,4 @@ | |||||
| package com.databasir.core.doc.render.markdown; | package com.databasir.core.render.markdown; | ||||
| 
 | 
 | ||||
| import java.util.List; | import java.util.List; | ||||
| 
 | 
 | ||||
| @ -1,43 +1,36 @@ | |||||
| import com.databasir.core.doc.factory.DatabaseDocConfig; | import com.databasir.core.Databasir; | ||||
| import com.databasir.core.doc.factory.extension.mysql.MysqlTableTriggerDocFactory; | import com.databasir.core.meta.pojo.DatabaseMeta; | ||||
| 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.RenderConfig; |  | ||||
| 
 | 
 | ||||
| import java.io.FileOutputStream; | import java.io.FileOutputStream; | ||||
| import java.io.IOException; | import java.io.IOException; | ||||
|  | import java.sql.Connection; | ||||
| import java.sql.DriverManager; | import java.sql.DriverManager; | ||||
| import java.sql.SQLException; | import java.sql.SQLException; | ||||
| import java.util.Properties; | import java.util.Properties; | ||||
| 
 | 
 | ||||
| public class App { | public class App { | ||||
|     public static void main(String[] args) throws SQLException, ClassNotFoundException { |     public static void main(String[] args) throws SQLException, ClassNotFoundException { | ||||
|  |         try (FileOutputStream out = new FileOutputStream("user.md")) { | ||||
|  |             Connection connection = getJdbcConnection(); | ||||
|  |             Databasir databasir = Databasir.of(); | ||||
|  |             DatabaseMeta doc = databasir.get(connection, "user").orElseThrow(); | ||||
|  |             databasir.renderAsMarkdown(doc, out); | ||||
|  |         } catch (IOException e) { | ||||
|  |             throw new IllegalStateException(e); | ||||
|  |         } | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     private static Connection getJdbcConnection() throws SQLException, ClassNotFoundException { | ||||
|         // get database connection
 |         // get database connection
 | ||||
|         Class.forName("com.mysql.cj.jdbc.Driver"); |         Class.forName("com.mysql.cj.jdbc.Driver"); | ||||
|  | 
 | ||||
|         Properties info = new Properties(); |         Properties info = new Properties(); | ||||
|         info.put("user", "root"); |         info.put("user", "root"); | ||||
|         info.put("password", "123456"); |         info.put("password", "123456"); | ||||
|         // this config is used by mysql
 |         // this config is used by mysql
 | ||||
|         info.put("useInformationSchema", "true"); |         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 = DatabaseDocConfig.builder() |  | ||||
|                 .databaseName("user") |  | ||||
|                 .connection(connection) |  | ||||
|                 .tableTriggerDocFactory(new MysqlTableTriggerDocFactory()) |  | ||||
|                 .build(); |  | ||||
|         DatabaseDoc doc = JdbcDatabaseDocFactory.of().create(config).orElseThrow(); |  | ||||
| 
 | 
 | ||||
|         // render as markdown
 |         String url = "jdbc:mysql://localhost:3306/patient?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true"; | ||||
|         try (FileOutputStream out = new FileOutputStream("user.md")) { |         return DriverManager.getConnection(url, info); | ||||
|             RenderConfig renderConfig = new RenderConfig(); |  | ||||
|             renderConfig.setRenderTriggers(true); |  | ||||
|             Render.markdownRender(renderConfig).rendering(doc, out); |  | ||||
|         } catch (IOException e) { |  | ||||
|             throw new IllegalStateException(e); |  | ||||
|         } |  | ||||
|     } |     } | ||||
| } | } | ||||
|  | |||||
					Loading…
					
					
				
		Reference in new issue