optimize: redesign table diff method (#227)
This commit is contained in:
parent
ebfe669a01
commit
1dc0e10210
|
@ -1,7 +1,7 @@
|
|||
package com.databasir.job;
|
||||
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.core.domain.document.service.DocumentService;
|
||||
import com.databasir.core.domain.document.service.DocumentSyncService;
|
||||
import com.databasir.core.domain.log.data.OperationLogRequest;
|
||||
import com.databasir.core.domain.log.service.OperationLogService;
|
||||
import com.databasir.dao.enums.ProjectSyncTaskStatus;
|
||||
|
@ -26,7 +26,7 @@ import java.util.stream.Collectors;
|
|||
@Slf4j
|
||||
public class ProjectSyncTaskScheduler {
|
||||
|
||||
private final DocumentService documentService;
|
||||
private final DocumentSyncService documentSyncService;
|
||||
|
||||
private final OperationLogService operationLogService;
|
||||
|
||||
|
@ -64,7 +64,7 @@ public class ProjectSyncTaskScheduler {
|
|||
private void sync(Integer taskId, Integer groupId, Integer projectId, Integer userId) {
|
||||
try {
|
||||
updateSyncTaskStatus(taskId, ProjectSyncTaskStatus.RUNNING, "running");
|
||||
documentService.syncByProjectId(projectId);
|
||||
documentSyncService.syncByProjectId(projectId);
|
||||
updateSyncTaskStatus(taskId, ProjectSyncTaskStatus.FINISHED, "ok");
|
||||
saveOperationLog(groupId, projectId, userId, null);
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -48,9 +48,11 @@ subprojects {
|
|||
|
||||
implementation "org.projectlombok:lombok:${lombokVersion}"
|
||||
annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
|
||||
testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}"
|
||||
|
||||
implementation "org.mapstruct:mapstruct:${mapstructVersion}"
|
||||
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
|
||||
testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
|
||||
|
||||
implementation "org.slf4j:slf4j-api:${slf4jVersion}"
|
||||
implementation "com.alibaba:easyexcel:${easyExcelVersion}"
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
package com.databasir.core.domain.document.comparator;
|
||||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
public class DiffResult {
|
||||
|
||||
private final String id;
|
||||
|
||||
private final DiffType diffType;
|
||||
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
package com.databasir.core.domain.document.comparator;
|
||||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import com.databasir.core.meta.data.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class DocumentDiffs {
|
||||
|
||||
public static List<TableDiffResult> tableDiff(DatabaseMeta original, DatabaseMeta current) {
|
||||
return tableDiff(original.getTables(), current.getTables());
|
||||
}
|
||||
|
||||
public static List<TableDiffResult> tableDiff(List<TableMeta> original, List<TableMeta> current) {
|
||||
Map<String, TableMeta> originalTablesMap = toMap(original, TableMeta::getName);
|
||||
Map<String, TableMeta> currentTablesMap = toMap(current, TableMeta::getName);
|
||||
var added = added(originalTablesMap, currentTablesMap)
|
||||
.stream()
|
||||
.map(curr -> {
|
||||
TableDiffResult result = new TableDiffResult(curr.getName(), DiffType.ADDED);
|
||||
List<DiffResult> columnDiffs =
|
||||
doDiff(Collections.emptyList(), curr.getColumns(), ColumnMeta::getName);
|
||||
List<DiffResult> indexDiffs =
|
||||
doDiff(Collections.emptyList(), curr.getIndexes(), IndexMeta::getName);
|
||||
List<DiffResult> triggerDiffs =
|
||||
doDiff(Collections.emptyList(), curr.getTriggers(), TriggerMeta::getName);
|
||||
List<DiffResult> fkDiffs =
|
||||
doDiff(Collections.emptyList(),
|
||||
curr.getForeignKeys(),
|
||||
fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq());
|
||||
result.setColumnDiffResults(columnDiffs);
|
||||
result.setIndexDiffResults(indexDiffs);
|
||||
result.setTriggerDiffResults(triggerDiffs);
|
||||
result.setForeignKeyDiffResults(fkDiffs);
|
||||
return result;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
var removed = removed(originalTablesMap, currentTablesMap)
|
||||
.stream()
|
||||
.map(old -> {
|
||||
TableDiffResult result = new TableDiffResult(old.getName(), DiffType.REMOVED);
|
||||
List<DiffResult> columnDiffs =
|
||||
doDiff(old.getColumns(), Collections.emptyList(), ColumnMeta::getName);
|
||||
List<DiffResult> indexDiffs =
|
||||
doDiff(old.getIndexes(), Collections.emptyList(), IndexMeta::getName);
|
||||
List<DiffResult> triggerDiffs =
|
||||
doDiff(old.getTriggers(), Collections.emptyList(), TriggerMeta::getName);
|
||||
List<DiffResult> fkDiffs =
|
||||
doDiff(old.getForeignKeys(),
|
||||
Collections.emptyList(),
|
||||
fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq());
|
||||
result.setColumnDiffResults(columnDiffs);
|
||||
result.setIndexDiffResults(indexDiffs);
|
||||
result.setTriggerDiffResults(triggerDiffs);
|
||||
result.setForeignKeyDiffResults(fkDiffs);
|
||||
return result;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
List<TableDiffResult> sameOrModified = currentTablesMap.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> originalTablesMap.containsKey(entry.getKey()))
|
||||
.map(entry -> {
|
||||
String tableName = entry.getKey();
|
||||
TableMeta currentTable = entry.getValue();
|
||||
TableMeta originalTable = originalTablesMap.get(tableName);
|
||||
|
||||
List<DiffResult> columnDiffs =
|
||||
doDiff(originalTable.getColumns(), currentTable.getColumns(), ColumnMeta::getName);
|
||||
List<DiffResult> indexDiffs =
|
||||
doDiff(originalTable.getIndexes(), currentTable.getIndexes(), IndexMeta::getName);
|
||||
List<DiffResult> triggerDiffs =
|
||||
doDiff(originalTable.getTriggers(), currentTable.getTriggers(), TriggerMeta::getName);
|
||||
List<DiffResult> fkDiffs =
|
||||
doDiff(originalTable.getForeignKeys(),
|
||||
currentTable.getForeignKeys(),
|
||||
fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq());
|
||||
|
||||
DiffType diffType = Objects.equals(currentTable, originalTable) ? DiffType.NONE : DiffType.MODIFIED;
|
||||
TableDiffResult diffResult = new TableDiffResult(tableName, diffType);
|
||||
diffResult.setColumnDiffResults(columnDiffs);
|
||||
diffResult.setIndexDiffResults(indexDiffs);
|
||||
diffResult.setTriggerDiffResults(triggerDiffs);
|
||||
diffResult.setForeignKeyDiffResults(fkDiffs);
|
||||
return diffResult;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<TableDiffResult> all = new ArrayList<>(16);
|
||||
all.addAll(sameOrModified);
|
||||
all.addAll(added);
|
||||
all.addAll(removed);
|
||||
return all;
|
||||
}
|
||||
|
||||
private static <T> List<DiffResult> doDiff(List<T> original, List<T> current, Function<T, String> idMapping) {
|
||||
Map<String, T> originalColumnMap = toMap(original, idMapping);
|
||||
Map<String, T> currentColumnMap = toMap(current, idMapping);
|
||||
BiFunction<T, DiffType, DiffResult> mapping = (meta, diffType) -> {
|
||||
return new DiffResult(idMapping.apply(meta), diffType);
|
||||
};
|
||||
List<DiffResult> added = added(originalColumnMap, currentColumnMap)
|
||||
.stream()
|
||||
.map(col -> mapping.apply(col, DiffType.ADDED))
|
||||
.collect(Collectors.toList());
|
||||
List<DiffResult> removed = removed(originalColumnMap, currentColumnMap)
|
||||
.stream()
|
||||
.map(col -> mapping.apply(col, DiffType.REMOVED))
|
||||
.collect(Collectors.toList());
|
||||
List<DiffResult> modified = modified(originalColumnMap, currentColumnMap)
|
||||
.stream()
|
||||
.map(col -> mapping.apply(col, DiffType.MODIFIED))
|
||||
.collect(Collectors.toList());
|
||||
List<DiffResult> same = same(originalColumnMap, currentColumnMap)
|
||||
.stream()
|
||||
.map(col -> mapping.apply(col, DiffType.NONE))
|
||||
.collect(Collectors.toList());
|
||||
List<DiffResult> columnResults = new ArrayList<>();
|
||||
columnResults.addAll(same);
|
||||
columnResults.addAll(added);
|
||||
columnResults.addAll(modified);
|
||||
columnResults.addAll(removed);
|
||||
return columnResults;
|
||||
}
|
||||
|
||||
private static <T> Map<String, T> toMap(List<T> content, Function<T, String> idMapping) {
|
||||
return content
|
||||
.stream()
|
||||
.collect(Collectors.toMap(idMapping, Function.identity(), (a, b) -> {
|
||||
log.warn("Duplicate key, origin = {}, current = {}", a, b);
|
||||
return a;
|
||||
}));
|
||||
}
|
||||
|
||||
private static <T> List<T> added(Map<String, T> originalMapById,
|
||||
Map<String, T> currentMapById) {
|
||||
return currentMapById.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> !originalMapById.containsKey(entry.getKey()))
|
||||
.map(Map.Entry::getValue)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static <T> List<T> removed(Map<String, T> originalMapById,
|
||||
Map<String, T> currentMapById) {
|
||||
return originalMapById.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> !currentMapById.containsKey(entry.getKey()))
|
||||
.map(Map.Entry::getValue)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static <T> List<T> modified(Map<String, T> originalMapById,
|
||||
Map<String, T> currentMapById) {
|
||||
return modified(originalMapById, currentMapById, Objects::equals);
|
||||
}
|
||||
|
||||
private static <T> List<T> modified(Map<String, T> originalMapById,
|
||||
Map<String, T> currentMapById,
|
||||
BiFunction<T, T, Boolean> sameFunction) {
|
||||
return currentMapById.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> originalMapById.containsKey(entry.getKey()))
|
||||
.filter(entry -> !sameFunction.apply(entry.getValue(), originalMapById.get(entry.getKey())))
|
||||
.map(Map.Entry::getValue)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static <T> List<T> same(Map<String, T> originalMapById,
|
||||
Map<String, T> currentMapById) {
|
||||
return same(originalMapById, currentMapById, Objects::equals);
|
||||
}
|
||||
|
||||
private static <T> List<T> same(Map<String, T> originalMapById,
|
||||
Map<String, T> currentMapById,
|
||||
BiFunction<T, T, Boolean> sameFunction) {
|
||||
return currentMapById.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> originalMapById.containsKey(entry.getKey()))
|
||||
.filter(entry -> sameFunction.apply(entry.getValue(), originalMapById.get(entry.getKey())))
|
||||
.map(Map.Entry::getValue)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package com.databasir.core.domain.document.comparator;
|
||||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
public class TableDiffResult {
|
||||
|
||||
private final String id;
|
||||
|
||||
private final DiffType diffType;
|
||||
|
||||
private Collection<DiffResult> columnDiffResults = Collections.emptyList();
|
||||
|
||||
private Collection<DiffResult> indexDiffResults = Collections.emptyList();
|
||||
|
||||
private Collection<DiffResult> triggerDiffResults = Collections.emptyList();
|
||||
|
||||
private Collection<DiffResult> foreignKeyDiffResults = Collections.emptyList();
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package com.databasir.core.domain.document.converter;
|
||||
|
||||
import com.databasir.core.domain.document.data.diff.*;
|
||||
import com.databasir.dao.tables.pojos.*;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface DocumentDiffConverter {
|
||||
|
||||
default DatabaseDocDiff of(DatabaseDocument databaseDocument,
|
||||
List<TableDocument> tables,
|
||||
List<TableColumnDocument> columns,
|
||||
List<TableIndexDocument> indexes,
|
||||
List<TableTriggerDocument> triggers,
|
||||
List<TableForeignKeyDocument> foreignKeys) {
|
||||
List<TableDocDiff> tableDocDiffList = of(tables, columns, indexes, triggers, foreignKeys);
|
||||
return of(databaseDocument, tableDocDiffList);
|
||||
}
|
||||
|
||||
default List<TableDocDiff> of(List<TableDocument> tables,
|
||||
List<TableColumnDocument> columns,
|
||||
List<TableIndexDocument> indexes,
|
||||
List<TableTriggerDocument> triggers,
|
||||
List<TableForeignKeyDocument> foreignKeys) {
|
||||
var columnGroupByTableId = groupBy(columns, TableColumnDocument::getTableDocumentId);
|
||||
var indexGroupByTableId = groupBy(indexes, TableIndexDocument::getTableDocumentId);
|
||||
var triggerGroupByTableId = groupBy(triggers, TableTriggerDocument::getTableDocumentId);
|
||||
var fkGroupByTableId = groupBy(foreignKeys, TableForeignKeyDocument::getTableDocumentId);
|
||||
return tables.stream()
|
||||
.map(table -> {
|
||||
Integer id = table.getId();
|
||||
var tableCols = columnGroupByTableId.getOrDefault(id, Collections.emptyList());
|
||||
var tableIndexes = indexGroupByTableId.getOrDefault(id, Collections.emptyList());
|
||||
var tableTriggers = triggerGroupByTableId.getOrDefault(id, Collections.emptyList());
|
||||
var tableForeignKeys = fkGroupByTableId.getOrDefault(id, Collections.emptyList());
|
||||
return of(table, tableCols, tableIndexes, tableTriggers, tableForeignKeys);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
DatabaseDocDiff of(DatabaseDocument databaseDocument, List<TableDocDiff> tables);
|
||||
|
||||
TableDocDiff of(TableDocument table,
|
||||
List<TableColumnDocument> columns,
|
||||
List<TableIndexDocument> indexes,
|
||||
List<TableTriggerDocument> triggers,
|
||||
List<TableForeignKeyDocument> foreignKeys);
|
||||
|
||||
ColumnDocDiff of(TableColumnDocument pojo);
|
||||
|
||||
IndexDocDiff of(TableIndexDocument pojo);
|
||||
|
||||
TriggerDocDiff of(TableTriggerDocument pojo);
|
||||
|
||||
ForeignKeyDocDiff of(TableForeignKeyDocument pojo);
|
||||
|
||||
default <R> Map<Integer, List<R>> groupBy(List<R> content, Function<R, Integer> idMapping) {
|
||||
return content.stream()
|
||||
.collect(Collectors.groupingBy(idMapping));
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@ package com.databasir.core.domain.document.converter;
|
|||
|
||||
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
|
||||
import com.databasir.core.domain.document.data.TableDocumentResponse;
|
||||
import com.databasir.core.domain.document.data.diff.ColumnDocDiff;
|
||||
import com.databasir.core.domain.document.data.diff.TableDocDiff;
|
||||
import com.databasir.core.infrastructure.converter.JsonConverter;
|
||||
import com.databasir.dao.tables.pojos.*;
|
||||
import org.mapstruct.Mapper;
|
||||
|
@ -64,4 +66,47 @@ public interface DocumentResponseConverter {
|
|||
@Mapping(target = "documentVersion", source = "databaseDocument.version")
|
||||
DatabaseDocumentResponse of(DatabaseDocument databaseDocument,
|
||||
List<TableDocumentResponse> tables);
|
||||
|
||||
default TableDocumentResponse ofDiff(TableDocDiff table,
|
||||
Map<String, Integer> discussionCountMap,
|
||||
Map<String, String> descriptionMap) {
|
||||
List<TableDocumentResponse.ColumnDocumentResponse> cols = toColumns(table, discussionCountMap, descriptionMap);
|
||||
return ofDiff(table, cols, discussionCountMap, descriptionMap);
|
||||
}
|
||||
|
||||
@Mapping(target = "description", expression = "java(descriptionMap.get(table.getName()))")
|
||||
@Mapping(target = "discussionCount", expression = "java(discussionCountMap.get(table.getName()))")
|
||||
@Mapping(target = "columns", source = "cols")
|
||||
TableDocumentResponse ofDiff(TableDocDiff table,
|
||||
List<TableDocumentResponse.ColumnDocumentResponse> cols,
|
||||
Map<String, Integer> discussionCountMap,
|
||||
Map<String, String> descriptionMap);
|
||||
|
||||
default List<TableDocumentResponse.ColumnDocumentResponse> toColumns(TableDocDiff table,
|
||||
Map<String, Integer> discussionCountMap,
|
||||
Map<String, String> descriptionMap) {
|
||||
return table.getColumns()
|
||||
.stream()
|
||||
.map(col -> toColumn(table.getName(), col, discussionCountMap, descriptionMap))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Mapping(target = "description", expression = "java(descriptionMap.get(tableName+\".\"+diff.getName()))")
|
||||
@Mapping(target = "discussionCount", expression = "java(discussionCountMap.get(tableName+\".\"+diff.getName()))")
|
||||
@Mapping(target = "original",
|
||||
expression = "java(toOriginalColumn(tableName, diff.getOriginal(), discussionCountMap, descriptionMap))")
|
||||
TableDocumentResponse.ColumnDocumentResponse toColumn(String tableName,
|
||||
ColumnDocDiff diff,
|
||||
Map<String, Integer> discussionCountMap,
|
||||
Map<String, String> descriptionMap);
|
||||
|
||||
default TableDocumentResponse.ColumnDocumentResponse toOriginalColumn(String tableName,
|
||||
ColumnDocDiff diff,
|
||||
Map<String, Integer> discussionCountMap,
|
||||
Map<String, String> descriptionMap) {
|
||||
if (diff == null) {
|
||||
return null;
|
||||
}
|
||||
return toColumn(tableName, diff, discussionCountMap, descriptionMap);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.databasir.core.domain.document.converter;
|
|||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import com.databasir.core.domain.document.data.DatabaseDocumentSimpleResponse;
|
||||
import com.databasir.core.domain.document.data.diff.TableDocDiff;
|
||||
import com.databasir.core.infrastructure.converter.JsonConverter;
|
||||
import com.databasir.dao.tables.pojos.DatabaseDocument;
|
||||
import com.databasir.dao.tables.pojos.TableDocument;
|
||||
|
@ -16,6 +17,11 @@ import java.util.stream.Collectors;
|
|||
@Mapper(componentModel = "spring", uses = JsonConverter.class, unmappedTargetPolicy = ReportingPolicy.WARN)
|
||||
public interface DocumentSimpleResponseConverter {
|
||||
|
||||
@Mapping(target = "id", source = "database.id")
|
||||
@Mapping(target = "createAt", source = "database.createAt")
|
||||
@Mapping(target = "documentVersion", source = "database.version")
|
||||
DatabaseDocumentSimpleResponse of(DatabaseDocument database, List<TableDocDiff> tables, String projectName);
|
||||
|
||||
@Mapping(target = "id", source = "databaseDocument.id")
|
||||
@Mapping(target = "createAt", source = "databaseDocument.createAt")
|
||||
@Mapping(target = "documentVersion", source = "databaseDocument.version")
|
||||
|
@ -32,7 +38,6 @@ public interface DocumentSimpleResponseConverter {
|
|||
Map<String, Integer> discussionCountMapByTableName,
|
||||
Map<String, String> descriptionMapByTableName) {
|
||||
return tables.stream()
|
||||
|
||||
.map(table -> {
|
||||
Integer count = discussionCountMapByTableName.get(table.getName());
|
||||
String description = descriptionMapByTableName.get(table.getName());
|
||||
|
@ -40,4 +45,22 @@ public interface DocumentSimpleResponseConverter {
|
|||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
default List<DatabaseDocumentSimpleResponse.TableData> ofDiff(List<TableDocDiff> tables,
|
||||
Map<String, Integer> discussionCountMapByTableName,
|
||||
Map<String, String> descriptionMapByTableName) {
|
||||
return tables.stream()
|
||||
.map(tableDocDiff -> {
|
||||
String tableName = tableDocDiff.getName();
|
||||
Integer count = discussionCountMapByTableName.get(tableName);
|
||||
String description = descriptionMapByTableName.get(tableName);
|
||||
return ofDiff(tableDocDiff, count, description);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
DatabaseDocumentSimpleResponse.TableData ofDiff(TableDocDiff table,
|
||||
Integer discussionCount,
|
||||
String description);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.databasir.core.domain.document.data;
|
||||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import com.databasir.core.domain.document.data.diff.DiffAble;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
@ -48,5 +49,6 @@ public class DatabaseDocumentSimpleResponse {
|
|||
private DiffType diffType;
|
||||
|
||||
private TableData original;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.databasir.core.domain.document.data;
|
||||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import com.databasir.core.domain.document.data.diff.DiffAble;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package com.databasir.core.domain.document.data.diff;
|
||||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class ColumnDocDiff implements DiffAble<ColumnDocDiff> {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private Integer tableDocumentId;
|
||||
|
||||
private Integer databaseDocumentId;
|
||||
|
||||
private String name;
|
||||
|
||||
private String type;
|
||||
|
||||
private Integer dataType;
|
||||
|
||||
private String comment;
|
||||
|
||||
private String defaultValue;
|
||||
|
||||
private Integer size;
|
||||
|
||||
private Integer decimalDigits;
|
||||
|
||||
private Boolean isPrimaryKey;
|
||||
|
||||
private String nullable;
|
||||
|
||||
private String autoIncrement;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
private DiffType diffType;
|
||||
|
||||
private ColumnDocDiff original;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.databasir.core.domain.document.data.diff;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class DatabaseDocDiff {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private Integer projectId;
|
||||
|
||||
private String databaseName;
|
||||
|
||||
private String schemaName;
|
||||
|
||||
private String productName;
|
||||
|
||||
private String productVersion;
|
||||
|
||||
private Long version;
|
||||
|
||||
private List<TableDocDiff> tables = Collections.emptyList();
|
||||
|
||||
private LocalDateTime updateAt;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.databasir.core.domain.document.data;
|
||||
package com.databasir.core.domain.document.data.diff;
|
||||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.databasir.core.domain.document.data.diff;
|
||||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class ForeignKeyDocDiff implements DiffAble<ForeignKeyDocDiff> {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private Integer tableDocumentId;
|
||||
|
||||
private Integer databaseDocumentId;
|
||||
|
||||
private Integer keySeq;
|
||||
|
||||
private String fkName;
|
||||
|
||||
private String fkTableName;
|
||||
|
||||
private String fkColumnName;
|
||||
|
||||
private String pkName;
|
||||
|
||||
private String pkTableName;
|
||||
|
||||
private String pkColumnName;
|
||||
|
||||
private String updateRule;
|
||||
|
||||
private String deleteRule;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
private DiffType diffType;
|
||||
|
||||
private ForeignKeyDocDiff original;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.databasir.core.domain.document.data.diff;
|
||||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import lombok.Data;
|
||||
import org.jooq.JSON;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class IndexDocDiff implements DiffAble<IndexDocDiff> {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private Integer tableDocumentId;
|
||||
|
||||
private Integer databaseDocumentId;
|
||||
|
||||
private String name;
|
||||
|
||||
private Boolean isUnique;
|
||||
|
||||
private JSON columnNameArray;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
private DiffType diffType;
|
||||
|
||||
private IndexDocDiff original;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.databasir.core.domain.document.data.diff;
|
||||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class TableDocDiff implements DiffAble<TableDocDiff> {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String type;
|
||||
|
||||
private String comment;
|
||||
|
||||
@Builder.Default
|
||||
private List<ColumnDocDiff> columns = Collections.emptyList();
|
||||
|
||||
@Builder.Default
|
||||
private List<IndexDocDiff> indexes = Collections.emptyList();
|
||||
|
||||
@Builder.Default
|
||||
private List<TriggerDocDiff> triggers = Collections.emptyList();
|
||||
|
||||
@Builder.Default
|
||||
private List<ForeignKeyDocDiff> foreignKeys = Collections.emptyList();
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
private DiffType diffType;
|
||||
|
||||
private TableDocDiff original;
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.databasir.core.domain.document.data.diff;
|
||||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class TriggerDocDiff implements DiffAble<TriggerDocDiff> {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
private Integer tableDocumentId;
|
||||
|
||||
private Integer databaseDocumentId;
|
||||
|
||||
private String timing;
|
||||
|
||||
private String manipulation;
|
||||
|
||||
private String statement;
|
||||
|
||||
private String triggerCreateAt;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
private DiffType diffType;
|
||||
|
||||
private TriggerDocDiff original;
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package com.databasir.core.domain.document.diff;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class BaseTypeFieldEqualFunction implements BiFunction<Object, Object, Boolean> {
|
||||
|
||||
private final List<String> ignoreFields;
|
||||
|
||||
@Override
|
||||
public Boolean apply(Object that, Object other) {
|
||||
if (Objects.equals(that, other)) {
|
||||
return true;
|
||||
}
|
||||
if (that == null || other == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
BeanInfo thatBean = Introspector.getBeanInfo(that.getClass());
|
||||
BeanInfo otherBean = Introspector.getBeanInfo(other.getClass());
|
||||
Map<String, PropertyDescriptor> otherBeanPropertyMap = Arrays.stream(otherBean.getPropertyDescriptors())
|
||||
.collect(Collectors.toMap(PropertyDescriptor::getName, p -> p));
|
||||
for (PropertyDescriptor thatProperty : thatBean.getPropertyDescriptors()) {
|
||||
if (thatProperty.getReadMethod() == null || thatProperty.getWriteMethod() == null) {
|
||||
continue;
|
||||
}
|
||||
if (ignoreFields.contains(thatProperty.getName())) {
|
||||
continue;
|
||||
}
|
||||
if (!otherBeanPropertyMap.containsKey(thatProperty.getName())) {
|
||||
return false;
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(thatProperty.getPropertyType())) {
|
||||
Collection thatValue = (Collection) thatProperty.getReadMethod().invoke(that);
|
||||
Collection otherValue = (Collection) otherBeanPropertyMap.get(thatProperty.getName())
|
||||
.getReadMethod().invoke(other);
|
||||
return handleCollection(thatValue, otherValue);
|
||||
}
|
||||
if (!thatProperty.getPropertyType().isPrimitive()) {
|
||||
Object thatValue = thatProperty.getReadMethod().invoke(that);
|
||||
Object otherValue = otherBeanPropertyMap.get(thatProperty.getName()).getReadMethod().invoke(other);
|
||||
if (!apply(thatValue, otherValue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Object thatValue = thatProperty.getReadMethod().invoke(that);
|
||||
Object otherValue = otherBeanPropertyMap.get(thatProperty.getName()).getReadMethod().invoke(other);
|
||||
if (!Objects.equals(thatValue, otherValue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
|
||||
log.error("Error comparing objects", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleCollection(Collection<Object> that, Collection<Object> other) {
|
||||
if (that.size() != other.size()) {
|
||||
return false;
|
||||
}
|
||||
for (Object thatObj : that) {
|
||||
boolean anyMatch = other.stream().anyMatch(otherObj -> this.apply(thatObj, otherObj));
|
||||
if (!anyMatch) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
package com.databasir.core.domain.document.diff;
|
||||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import com.databasir.core.domain.document.comparator.DiffResult;
|
||||
import com.databasir.core.domain.document.comparator.TableDiffResult;
|
||||
import com.databasir.core.domain.document.data.DiffAble;
|
||||
import com.databasir.core.domain.document.data.TableDocumentResponse;
|
||||
import com.databasir.core.domain.document.data.TableDocumentResponse.ForeignKeyDocumentResponse;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
public class DiffTypeFills {
|
||||
|
||||
public static TableDocumentResponse fillRemoved(TableDocumentResponse data) {
|
||||
data.setDiffType(DiffType.REMOVED);
|
||||
data.getColumns().forEach(col -> col.setDiffType(DiffType.REMOVED));
|
||||
data.getForeignKeys().forEach(col -> col.setDiffType(DiffType.REMOVED));
|
||||
data.getIndexes().forEach(col -> col.setDiffType(DiffType.REMOVED));
|
||||
data.getTriggers().forEach(col -> col.setDiffType(DiffType.REMOVED));
|
||||
return data;
|
||||
}
|
||||
|
||||
public static TableDocumentResponse fillAdded(TableDocumentResponse data, TableDiffResult diff) {
|
||||
data.setDiffType(DiffType.ADDED);
|
||||
var cols =
|
||||
diff(diff.getColumnDiffResults(), emptyList(), data.getColumns(), i -> i.getName());
|
||||
data.setColumns(cols);
|
||||
var indexes =
|
||||
diff(diff.getIndexDiffResults(), emptyList(), data.getIndexes(), i -> i.getName());
|
||||
data.setIndexes(indexes);
|
||||
var foreignKeys = foreignKeyDiff(diff.getForeignKeyDiffResults(),
|
||||
emptyList(), data.getForeignKeys());
|
||||
data.setForeignKeys(foreignKeys);
|
||||
var triggers =
|
||||
diff(diff.getTriggerDiffResults(), emptyList(), data.getTriggers(), t -> t.getName());
|
||||
data.setTriggers(triggers);
|
||||
return data;
|
||||
}
|
||||
|
||||
public static TableDocumentResponse fillModified(TableDocumentResponse current,
|
||||
TableDocumentResponse original,
|
||||
TableDiffResult diff) {
|
||||
current.setDiffType(DiffType.MODIFIED);
|
||||
current.setOriginal(original);
|
||||
var cols =
|
||||
diff(diff.getColumnDiffResults(), original.getColumns(), current.getColumns(),
|
||||
col -> col.getName());
|
||||
current.setColumns(cols);
|
||||
var indexes =
|
||||
diff(diff.getIndexDiffResults(), original.getIndexes(), current.getIndexes(), i -> i.getName());
|
||||
current.setIndexes(indexes);
|
||||
var foreignKeys = foreignKeyDiff(diff.getForeignKeyDiffResults(),
|
||||
original.getForeignKeys(), current.getForeignKeys());
|
||||
current.setForeignKeys(foreignKeys);
|
||||
var triggers =
|
||||
diff(diff.getTriggerDiffResults(), original.getTriggers(), current.getTriggers(),
|
||||
t -> t.getName());
|
||||
current.setTriggers(triggers);
|
||||
return current;
|
||||
}
|
||||
|
||||
public static <T extends DiffAble> List<T> diff(Collection<DiffResult> diffs,
|
||||
Collection<T> original,
|
||||
Collection<T> current,
|
||||
Function<T, String> idMapping) {
|
||||
var currentMapByName = current.stream()
|
||||
.collect(Collectors.toMap(idMapping, Function.identity(), (a, b) -> a));
|
||||
var originalMapByName = original.stream()
|
||||
.collect(Collectors.toMap(idMapping, Function.identity(), (a, b) -> a));
|
||||
return diffs.stream().map(diff -> {
|
||||
if (diff.getDiffType() == DiffType.ADDED) {
|
||||
var t = currentMapByName.get(diff.getId());
|
||||
t.setDiffType(DiffType.ADDED);
|
||||
return t;
|
||||
}
|
||||
if (diff.getDiffType() == DiffType.REMOVED) {
|
||||
var t = originalMapByName.get(diff.getId());
|
||||
t.setDiffType(DiffType.REMOVED);
|
||||
return t;
|
||||
}
|
||||
if (diff.getDiffType() == DiffType.MODIFIED) {
|
||||
var c = currentMapByName.get(diff.getId());
|
||||
var o = originalMapByName.get(diff.getId());
|
||||
c.setDiffType(DiffType.MODIFIED);
|
||||
c.setOriginal(o);
|
||||
return c;
|
||||
}
|
||||
var t = currentMapByName.get(diff.getId());
|
||||
t.setDiffType(DiffType.NONE);
|
||||
return t;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<ForeignKeyDocumentResponse> foreignKeyDiff(Collection<DiffResult> diffs,
|
||||
Collection<ForeignKeyDocumentResponse> original,
|
||||
Collection<ForeignKeyDocumentResponse> current) {
|
||||
Function<ForeignKeyDocumentResponse, String> idMapping = fk -> {
|
||||
return fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq();
|
||||
};
|
||||
return diff(diffs, original, current, idMapping);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
package com.databasir.core.domain.document.diff;
|
||||
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import com.databasir.core.domain.document.data.diff.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DocumentDiffChecker {
|
||||
|
||||
private static final List<String> IGNORE_FIELDS = List.of(
|
||||
"id",
|
||||
"class",
|
||||
"tableDocumentId",
|
||||
"databaseDocumentId",
|
||||
"createAt",
|
||||
"updateAt",
|
||||
"diffType",
|
||||
"original"
|
||||
);
|
||||
|
||||
public List<TableDocDiff> diff(List<TableDocDiff> original, List<TableDocDiff> current) {
|
||||
Map<String, TableDocDiff> originalTablesMap = toMap(original, TableDocDiff::getName);
|
||||
Map<String, TableDocDiff> currentTablesMap = toMap(current, TableDocDiff::getName);
|
||||
// added items
|
||||
var added = added(originalTablesMap, currentTablesMap)
|
||||
.stream()
|
||||
.map(curr -> {
|
||||
List<ColumnDocDiff> columnDiffs =
|
||||
doDiff(Collections.emptyList(), curr.getColumns(), ColumnDocDiff::getName);
|
||||
List<IndexDocDiff> indexDiffs =
|
||||
doDiff(Collections.emptyList(), curr.getIndexes(), IndexDocDiff::getName);
|
||||
List<TriggerDocDiff> triggerDiffs =
|
||||
doDiff(Collections.emptyList(), curr.getTriggers(), TriggerDocDiff::getName);
|
||||
List<ForeignKeyDocDiff> fkDiffs =
|
||||
doDiff(Collections.emptyList(),
|
||||
curr.getForeignKeys(),
|
||||
fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq());
|
||||
return TableDocDiff.builder()
|
||||
.id(curr.getId())
|
||||
.diffType(DiffType.ADDED)
|
||||
.name(curr.getName())
|
||||
.comment(curr.getComment())
|
||||
.type(curr.getType())
|
||||
.createAt(curr.getCreateAt())
|
||||
.columns(columnDiffs)
|
||||
.indexes(indexDiffs)
|
||||
.triggers(triggerDiffs)
|
||||
.foreignKeys(fkDiffs)
|
||||
.build();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
// removed items
|
||||
var removed = removed(originalTablesMap, currentTablesMap)
|
||||
.stream()
|
||||
.map(old -> {
|
||||
List<ColumnDocDiff> columnDiffs =
|
||||
doDiff(old.getColumns(), Collections.emptyList(), ColumnDocDiff::getName);
|
||||
List<IndexDocDiff> indexDiffs =
|
||||
doDiff(old.getIndexes(), Collections.emptyList(), IndexDocDiff::getName);
|
||||
List<TriggerDocDiff> triggerDiffs =
|
||||
doDiff(old.getTriggers(), Collections.emptyList(), TriggerDocDiff::getName);
|
||||
List<ForeignKeyDocDiff> fkDiffs =
|
||||
doDiff(old.getForeignKeys(),
|
||||
Collections.emptyList(),
|
||||
fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq());
|
||||
return TableDocDiff.builder()
|
||||
.id(old.getId())
|
||||
.diffType(DiffType.REMOVED)
|
||||
.name(old.getName())
|
||||
.comment(old.getComment())
|
||||
.type(old.getType())
|
||||
.createAt(old.getCreateAt())
|
||||
.columns(columnDiffs)
|
||||
.indexes(indexDiffs)
|
||||
.triggers(triggerDiffs)
|
||||
.foreignKeys(fkDiffs)
|
||||
.build();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
// unchanged or modified items
|
||||
List<TableDocDiff> sameOrModified = currentTablesMap.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> originalTablesMap.containsKey(entry.getKey()))
|
||||
.map(entry -> {
|
||||
String tableName = entry.getKey();
|
||||
TableDocDiff currentTable = entry.getValue();
|
||||
TableDocDiff originalTable = originalTablesMap.get(tableName);
|
||||
|
||||
List<ColumnDocDiff> columnDiffs =
|
||||
doDiff(originalTable.getColumns(), currentTable.getColumns(), ColumnDocDiff::getName);
|
||||
List<IndexDocDiff> indexDiffs =
|
||||
doDiff(originalTable.getIndexes(), currentTable.getIndexes(), IndexDocDiff::getName);
|
||||
List<TriggerDocDiff> triggerDiffs =
|
||||
doDiff(originalTable.getTriggers(), currentTable.getTriggers(), TriggerDocDiff::getName);
|
||||
List<ForeignKeyDocDiff> fkDiffs =
|
||||
doDiff(originalTable.getForeignKeys(),
|
||||
currentTable.getForeignKeys(),
|
||||
fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq());
|
||||
|
||||
BaseTypeFieldEqualFunction eq = new BaseTypeFieldEqualFunction(IGNORE_FIELDS);
|
||||
DiffType diffType = eq.apply(currentTable, originalTable) ? DiffType.NONE : DiffType.MODIFIED;
|
||||
// workaround for diffType = NONE
|
||||
if (diffType == DiffType.NONE) {
|
||||
originalTable = null;
|
||||
}
|
||||
return TableDocDiff.builder()
|
||||
.id(currentTable.getId())
|
||||
.diffType(diffType)
|
||||
.original(originalTable)
|
||||
.name(currentTable.getName())
|
||||
.comment(currentTable.getComment())
|
||||
.type(currentTable.getType())
|
||||
.createAt(currentTable.getCreateAt())
|
||||
.columns(columnDiffs)
|
||||
.indexes(indexDiffs)
|
||||
.triggers(triggerDiffs)
|
||||
.foreignKeys(fkDiffs)
|
||||
.build();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<TableDocDiff> all = new ArrayList<>(16);
|
||||
all.addAll(sameOrModified);
|
||||
all.addAll(added);
|
||||
all.addAll(removed);
|
||||
return all;
|
||||
}
|
||||
|
||||
private <T extends DiffAble<T>> List<T> doDiff(List<T> original, List<T> current, Function<T, String> idMapping) {
|
||||
Map<String, T> originalMap = toMap(original, idMapping);
|
||||
Map<String, T> currentMap = toMap(current, idMapping);
|
||||
List<T> added = added(originalMap, currentMap);
|
||||
List<T> removed = removed(originalMap, currentMap);
|
||||
List<T> modified = modified(originalMap, currentMap);
|
||||
List<T> same = same(originalMap, currentMap);
|
||||
List<T> results = new ArrayList<>();
|
||||
results.addAll(same);
|
||||
results.addAll(added);
|
||||
results.addAll(modified);
|
||||
results.addAll(removed);
|
||||
return results;
|
||||
}
|
||||
|
||||
private <T> Map<String, T> toMap(List<T> content, Function<T, String> idMapping) {
|
||||
return content
|
||||
.stream()
|
||||
.collect(Collectors.toMap(idMapping, Function.identity(), (a, b) -> {
|
||||
log.warn("Duplicate key, origin = {}, current = {}", a, b);
|
||||
return a;
|
||||
}));
|
||||
}
|
||||
|
||||
private <T extends DiffAble<T>> List<T> added(Map<String, T> originalMapById,
|
||||
Map<String, T> currentMapById) {
|
||||
return currentMapById.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> !originalMapById.containsKey(entry.getKey()))
|
||||
.map(Map.Entry::getValue)
|
||||
.map(value -> {
|
||||
value.setDiffType(DiffType.ADDED);
|
||||
return value;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private <T extends DiffAble<T>> List<T> removed(Map<String, T> originalMapById,
|
||||
Map<String, T> currentMapById) {
|
||||
return originalMapById.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> !currentMapById.containsKey(entry.getKey()))
|
||||
.map(Map.Entry::getValue)
|
||||
.map(value -> {
|
||||
value.setDiffType(DiffType.REMOVED);
|
||||
return value;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private <T extends DiffAble<T>> List<T> modified(Map<String, T> originalMapById,
|
||||
Map<String, T> currentMapById) {
|
||||
BaseTypeFieldEqualFunction eq = new BaseTypeFieldEqualFunction(IGNORE_FIELDS);
|
||||
return modified(originalMapById, currentMapById, eq);
|
||||
}
|
||||
|
||||
private <T extends DiffAble<T>> List<T> modified(Map<String, T> originalMapById,
|
||||
Map<String, T> currentMapById,
|
||||
BiFunction<Object, Object, Boolean> sameFunction) {
|
||||
return currentMapById.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> originalMapById.containsKey(entry.getKey()))
|
||||
.filter(entry -> !sameFunction.apply(entry.getValue(), originalMapById.get(entry.getKey())))
|
||||
.map(entry -> {
|
||||
T value = entry.getValue();
|
||||
value.setDiffType(DiffType.MODIFIED);
|
||||
value.setOriginal(originalMapById.get(entry.getKey()));
|
||||
return value;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private <T extends DiffAble<T>> List<T> same(Map<String, T> originalMapById,
|
||||
Map<String, T> currentMapById) {
|
||||
BaseTypeFieldEqualFunction eq = new BaseTypeFieldEqualFunction(IGNORE_FIELDS);
|
||||
return same(originalMapById, currentMapById, eq);
|
||||
}
|
||||
|
||||
private <T extends DiffAble<T>> List<T> same(Map<String, T> originalMapById,
|
||||
Map<String, T> currentMapById,
|
||||
BiFunction<Object, Object, Boolean> sameFunction) {
|
||||
return currentMapById.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> originalMapById.containsKey(entry.getKey()))
|
||||
.filter(entry -> sameFunction.apply(entry.getValue(), originalMapById.get(entry.getKey())))
|
||||
.map(entry -> {
|
||||
T value = entry.getValue();
|
||||
value.setDiffType(DiffType.NONE);
|
||||
return value;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
|
@ -1,27 +1,19 @@
|
|||
package com.databasir.core.domain.document.service;
|
||||
|
||||
import com.databasir.common.DatabasirException;
|
||||
import com.databasir.core.Databasir;
|
||||
import com.databasir.core.DatabasirConfig;
|
||||
import com.databasir.core.diff.Diffs;
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import com.databasir.core.diff.data.RootDiff;
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import com.databasir.core.domain.document.comparator.DocumentDiffs;
|
||||
import com.databasir.core.domain.document.comparator.TableDiffResult;
|
||||
import com.databasir.core.domain.document.converter.*;
|
||||
import com.databasir.core.domain.document.converter.DocumentDiffConverter;
|
||||
import com.databasir.core.domain.document.converter.DocumentResponseConverter;
|
||||
import com.databasir.core.domain.document.converter.DocumentSimpleResponseConverter;
|
||||
import com.databasir.core.domain.document.converter.TableResponseConverter;
|
||||
import com.databasir.core.domain.document.data.*;
|
||||
import com.databasir.core.domain.document.diff.DiffTypeFills;
|
||||
import com.databasir.core.domain.document.data.diff.DatabaseDocDiff;
|
||||
import com.databasir.core.domain.document.data.diff.TableDocDiff;
|
||||
import com.databasir.core.domain.document.diff.DiffTypePredictor;
|
||||
import com.databasir.core.domain.document.event.DocumentUpdated;
|
||||
import com.databasir.core.domain.document.diff.DocumentDiffChecker;
|
||||
import com.databasir.core.domain.document.generator.DocumentFileGenerator;
|
||||
import com.databasir.core.domain.document.generator.DocumentFileGenerator.DocumentFileGenerateContext;
|
||||
import com.databasir.core.domain.document.generator.DocumentFileType;
|
||||
import com.databasir.core.infrastructure.connection.DatabaseConnectionService;
|
||||
import com.databasir.core.infrastructure.converter.JsonConverter;
|
||||
import com.databasir.core.infrastructure.event.EventPublisher;
|
||||
import com.databasir.core.meta.data.DatabaseMeta;
|
||||
import com.databasir.core.meta.data.TableMeta;
|
||||
import com.databasir.dao.impl.*;
|
||||
import com.databasir.dao.tables.pojos.*;
|
||||
import com.databasir.dao.value.DocumentDiscussionCountPojo;
|
||||
|
@ -29,17 +21,12 @@ import lombok.RequiredArgsConstructor;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.jooq.tools.StringUtils;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
@ -49,18 +36,8 @@ import static java.util.Collections.emptyList;
|
|||
@Slf4j
|
||||
public class DocumentService {
|
||||
|
||||
private final GroupDao groupDao;
|
||||
|
||||
private final ProjectDao projectDao;
|
||||
|
||||
private final ProjectSyncRuleDao projectSyncRuleDao;
|
||||
|
||||
private final DataSourceDao dataSourceDao;
|
||||
|
||||
private final DataSourcePropertyDao dataSourcePropertyDao;
|
||||
|
||||
private final DatabaseConnectionService databaseConnectionService;
|
||||
|
||||
private final DatabaseDocumentDao databaseDocumentDao;
|
||||
|
||||
private final TableDocumentDao tableDocumentDao;
|
||||
|
@ -75,166 +52,19 @@ public class DocumentService {
|
|||
|
||||
private final DocumentDiscussionDao documentDiscussionDao;
|
||||
|
||||
private final DocumentFullTextDao documentFullTextDao;
|
||||
|
||||
private final DocumentDescriptionDao documentDescriptionDao;
|
||||
|
||||
private final DocumentPojoConverter documentPojoConverter;
|
||||
|
||||
private final DocumentFullTextConverter documentFullTextConverter;
|
||||
|
||||
private final DocumentResponseConverter documentResponseConverter;
|
||||
|
||||
private final DocumentSimpleResponseConverter documentSimpleResponseConverter;
|
||||
|
||||
private final DatabaseMetaConverter databaseMetaConverter;
|
||||
private final DocumentDiffConverter documentDiffConverter;
|
||||
|
||||
private final TableResponseConverter tableResponseConverter;
|
||||
|
||||
private final JsonConverter jsonConverter;
|
||||
|
||||
private final List<DocumentFileGenerator> documentFileGenerators;
|
||||
|
||||
private final EventPublisher eventPublisher;
|
||||
|
||||
@Transactional
|
||||
public void syncByProjectId(Integer projectId) {
|
||||
projectDao.selectOptionalById(projectId)
|
||||
.orElseThrow(DomainErrors.PROJECT_NOT_FOUND::exception);
|
||||
DatabaseMeta current = retrieveDatabaseMeta(projectId);
|
||||
Optional<DatabaseDocument> originalOption = databaseDocumentDao.selectNotArchivedByProjectId(projectId);
|
||||
if (originalOption.isPresent()) {
|
||||
DatabaseDocument original = originalOption.get();
|
||||
DatabaseMeta originalMeta = retrieveOriginalDatabaseMeta(original);
|
||||
RootDiff diff = Diffs.diff(originalMeta, current);
|
||||
if (diff.getDiffType() == DiffType.NONE) {
|
||||
log.info("ignore project {} {} sync data, because without change",
|
||||
projectId,
|
||||
original.getDatabaseName());
|
||||
return;
|
||||
}
|
||||
Integer previousDocumentId = original.getId();
|
||||
// archive old version
|
||||
databaseDocumentDao.updateIsArchiveById(previousDocumentId, true);
|
||||
Long version = original.getVersion();
|
||||
Integer docId = saveNewDocument(current, version + 1, original.getProjectId());
|
||||
eventPublisher.publish(new DocumentUpdated(diff, version + 1, version, projectId, docId));
|
||||
} else {
|
||||
Integer docId = saveNewDocument(current, 1L, projectId);
|
||||
RootDiff diff = null;
|
||||
try {
|
||||
diff = Diffs.diff(null, current);
|
||||
} catch (Exception e) {
|
||||
log.error("diff project " + projectId + " error, fallback diff type to NONE", e);
|
||||
}
|
||||
eventPublisher.publish(new DocumentUpdated(diff, 1L, null, projectId, docId));
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseMeta retrieveOriginalDatabaseMeta(DatabaseDocument original) {
|
||||
Integer docId = original.getId();
|
||||
List<TableDocument> tables = tableDocumentDao.selectByDatabaseDocumentId(docId);
|
||||
List<TableColumnDocument> columns = tableColumnDocumentDao.selectByDatabaseDocumentId(docId);
|
||||
List<TableIndexDocument> indexes = tableIndexDocumentDao.selectByDatabaseMetaId(docId);
|
||||
List<TableTriggerDocument> triggers = tableTriggerDocumentDao.selectByDatabaseDocumentId(docId);
|
||||
List<TableForeignKeyDocument> fks = tableForeignKeyDocumentDao.selectByDatabaseDocumentId(docId);
|
||||
return databaseMetaConverter.of(original, tables, columns, indexes, triggers, fks);
|
||||
}
|
||||
|
||||
private DatabaseMeta retrieveDatabaseMeta(Integer projectId) {
|
||||
ProjectSyncRule rule = projectSyncRuleDao.selectByProjectId(projectId);
|
||||
DataSource dataSource = dataSourceDao.selectByProjectId(projectId);
|
||||
List<DataSourceProperty> properties = dataSourcePropertyDao.selectByDataSourceId(dataSource.getId());
|
||||
Connection jdbcConnection = databaseConnectionService.create(dataSource, properties);
|
||||
DatabasirConfig databasirConfig = new DatabasirConfig();
|
||||
databasirConfig.setIgnoreTableNameRegex(jsonConverter.fromJson(rule.getIgnoreTableNameRegexArray()));
|
||||
databasirConfig.setIgnoreTableColumnNameRegex(jsonConverter.fromJson(rule.getIgnoreColumnNameRegexArray()));
|
||||
try {
|
||||
if (jdbcConnection == null) {
|
||||
throw DomainErrors.CONNECT_DATABASE_FAILED.exception();
|
||||
}
|
||||
DatabaseMeta databaseMeta = Databasir.of(databasirConfig)
|
||||
.get(jdbcConnection, dataSource.getDatabaseName(), dataSource.getSchemaName())
|
||||
.orElseThrow(DomainErrors.DATABASE_META_NOT_FOUND::exception);
|
||||
return databaseMeta;
|
||||
} finally {
|
||||
try {
|
||||
if (jdbcConnection != null && !jdbcConnection.isClosed()) {
|
||||
jdbcConnection.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("close jdbc connection error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Integer saveNewDocument(DatabaseMeta meta,
|
||||
Long version,
|
||||
Integer projectId) {
|
||||
|
||||
var dbDocPojo = documentPojoConverter.toDatabasePojo(projectId, meta, version);
|
||||
final Integer docId;
|
||||
try {
|
||||
docId = databaseDocumentDao.insertAndReturnId(dbDocPojo);
|
||||
dbDocPojo.setId(docId);
|
||||
} catch (DuplicateKeyException e) {
|
||||
log.warn("ignore insert database document projectId={} version={}", projectId, version);
|
||||
throw new DatabasirException(DomainErrors.DATABASE_DOCUMENT_DUPLICATE_KEY);
|
||||
}
|
||||
meta.getTables().forEach(table -> {
|
||||
TableDocument tableMeta =
|
||||
documentPojoConverter.toTablePojo(docId, table);
|
||||
Integer tableMetaId = tableDocumentDao.insertAndReturnId(tableMeta);
|
||||
tableMeta.setId(tableMetaId);
|
||||
// column
|
||||
var columns = documentPojoConverter.toColumnPojo(docId, tableMetaId, table.getColumns());
|
||||
tableColumnDocumentDao.batchInsert(columns);
|
||||
// index
|
||||
var indexes = documentPojoConverter.toIndexPojo(docId, tableMetaId, table.getIndexes())
|
||||
.stream()
|
||||
.filter(index -> {
|
||||
if (index.getName() != null) {
|
||||
return true;
|
||||
} else {
|
||||
log.warn("ignore table {} index {}, cause name is null", table.getName(), index);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
tableIndexDocumentDao.batchInsert(indexes);
|
||||
// foreign key
|
||||
var foreignKeys = documentPojoConverter.toForeignKeyPojo(docId, tableMetaId, table.getForeignKeys());
|
||||
tableForeignKeyDocumentDao.batchInsert(foreignKeys);
|
||||
|
||||
// trigger
|
||||
var triggers = documentPojoConverter.toTriggerPojo(docId, tableMetaId, table.getTriggers());
|
||||
tableTriggerDocumentDao.batchInsert(triggers);
|
||||
});
|
||||
log.info("save new version document success: projectId = {}, name = {}, version = {}",
|
||||
projectId, meta.getDatabaseName(), version);
|
||||
return docId;
|
||||
}
|
||||
|
||||
private void saveDocumentFullText(Integer projectId,
|
||||
DatabaseDocument database,
|
||||
TableDocument table,
|
||||
Map<String, String> descriptionMapByJoinName) {
|
||||
Project project = projectDao.selectById(projectId);
|
||||
Group group = groupDao.selectById(project.getGroupId());
|
||||
List<TableColumnDocument> columns = tableColumnDocumentDao.selectByTableDocumentId(table.getId());
|
||||
// clear outdated data before save
|
||||
documentFullTextDao.deleteTableFullText(table.getId());
|
||||
List<DocumentFullText> fullTextPojoList = columns.stream()
|
||||
.map(column -> {
|
||||
String tableName = table.getName();
|
||||
String tableDescription = descriptionMapByJoinName.get(tableName);
|
||||
String columnDescription = descriptionMapByJoinName.get(tableName + "." + column.getName());
|
||||
return documentFullTextConverter.toPojo(group, project, database, table, column,
|
||||
tableDescription, columnDescription);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
documentFullTextDao.batchInsert(fullTextPojoList);
|
||||
}
|
||||
private final DocumentDiffChecker documentDiffChecker;
|
||||
|
||||
public Optional<DatabaseDocumentSimpleResponse> getSimpleOneByProjectId(Integer projectId,
|
||||
Long version,
|
||||
|
@ -251,8 +81,6 @@ public class DocumentService {
|
|||
}
|
||||
|
||||
return documentOption.map(document -> {
|
||||
Integer id = document.getId();
|
||||
var tables = tableDocumentDao.selectByDatabaseDocumentId(id);
|
||||
var discussionCountMapByTableName =
|
||||
documentDiscussionDao.selectTableDiscussionCount(projectId)
|
||||
.stream()
|
||||
|
@ -261,59 +89,27 @@ public class DocumentService {
|
|||
documentDescriptionDao.selectTableDescriptionByProjectId(projectId)
|
||||
.stream()
|
||||
.collect(Collectors.toMap(d -> d.getTableName(), d -> d.getContent(), (a, b) -> a));
|
||||
var tableMetas = documentSimpleResponseConverter.of(
|
||||
tables,
|
||||
discussionCountMapByTableName,
|
||||
descriptionMapByTableName
|
||||
);
|
||||
|
||||
// if original version is not null means version diff enabled
|
||||
if (originalVersion != null) {
|
||||
var originalDocument =
|
||||
databaseDocumentDao.selectOptionalByProjectIdAndVersion(projectId, originalVersion)
|
||||
.orElseThrow(DomainErrors.DOCUMENT_VERSION_IS_INVALID::exception);
|
||||
var originalTables = tableDocumentDao.selectByDatabaseDocumentId(originalDocument.getId());
|
||||
var originalTableMetas = documentSimpleResponseConverter.of(
|
||||
originalTables,
|
||||
var diffResults = tableDiffs(projectId, originalVersion, version);
|
||||
var tableMetas = documentSimpleResponseConverter.ofDiff(
|
||||
diffResults, discussionCountMapByTableName, descriptionMapByTableName);
|
||||
DiffType diffType = DiffTypePredictor.predict(tableMetas);
|
||||
tableMetas.sort(Comparator.comparing(DatabaseDocumentSimpleResponse.TableData::getName));
|
||||
return documentSimpleResponseConverter.of(document, tableMetas, diffType, projectName);
|
||||
} else {
|
||||
Integer id = document.getId();
|
||||
var tables = tableDocumentDao.selectByDatabaseDocumentId(id);
|
||||
var tableMetas = documentSimpleResponseConverter.of(
|
||||
tables,
|
||||
discussionCountMapByTableName,
|
||||
descriptionMapByTableName
|
||||
);
|
||||
var originalMap = originalTableMetas.stream()
|
||||
.collect(Collectors.toMap(t -> t.getName(), Function.identity(), (a, b) -> a));
|
||||
var currentMap = tableMetas.stream()
|
||||
.collect(Collectors.toMap(t -> t.getName(), Function.identity(), (a, b) -> a));
|
||||
List<TableDiffResult> diffResults = tableDiffs(projectId, originalVersion, version);
|
||||
|
||||
List<DatabaseDocumentSimpleResponse.TableData> result = new ArrayList<>();
|
||||
for (TableDiffResult diffResult : diffResults) {
|
||||
var cur = currentMap.get(diffResult.getId());
|
||||
var org = originalMap.get(diffResult.getId());
|
||||
if (diffResult.getDiffType() == DiffType.ADDED) {
|
||||
cur.setDiffType(DiffType.ADDED);
|
||||
result.add(cur);
|
||||
} else if (diffResult.getDiffType() == DiffType.MODIFIED) {
|
||||
cur.setDiffType(DiffType.MODIFIED);
|
||||
cur.setOriginal(org);
|
||||
result.add(cur);
|
||||
} else if (diffResult.getDiffType() == DiffType.REMOVED) {
|
||||
org.setDiffType(DiffType.REMOVED);
|
||||
result.add(org);
|
||||
} else {
|
||||
cur.setDiffType(DiffType.NONE);
|
||||
result.add(cur);
|
||||
}
|
||||
}
|
||||
result.sort(Comparator.comparing(DatabaseDocumentSimpleResponse.TableData::getName));
|
||||
DiffType diffType = DiffTypePredictor.predict(result);
|
||||
return documentSimpleResponseConverter.of(document, result, diffType, projectName);
|
||||
} else {
|
||||
tableMetas.sort(Comparator.comparing(DatabaseDocumentSimpleResponse.TableData::getName));
|
||||
return documentSimpleResponseConverter.of(document, tableMetas, DiffType.NONE, projectName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public List<TableDiffResult> tableDiffs(Integer projectId, Long originalVersion, Long currentVersion) {
|
||||
public List<TableDocDiff> tableDiffs(Integer projectId, Long originalVersion, Long currentVersion) {
|
||||
var original = databaseDocumentDao.selectOptionalByProjectIdAndVersion(projectId, originalVersion)
|
||||
.orElseThrow(DomainErrors.DOCUMENT_VERSION_IS_INVALID::exception);
|
||||
DatabaseDocument current;
|
||||
|
@ -324,9 +120,9 @@ public class DocumentService {
|
|||
current = databaseDocumentDao.selectOptionalByProjectIdAndVersion(projectId, currentVersion)
|
||||
.orElseThrow(DomainErrors.DOCUMENT_VERSION_IS_INVALID::exception);
|
||||
}
|
||||
DatabaseMeta currMeta = retrieveOriginalDatabaseMeta(current);
|
||||
DatabaseMeta originalMeta = retrieveOriginalDatabaseMeta(original);
|
||||
return DocumentDiffs.tableDiff(originalMeta, currMeta);
|
||||
DatabaseDocDiff currentDiff = retrieveDatabaseDocumentDiff(current);
|
||||
DatabaseDocDiff originalDiff = retrieveDatabaseDocumentDiff(original);
|
||||
return documentDiffChecker.diff(originalDiff.getTables(), currentDiff.getTables());
|
||||
}
|
||||
|
||||
public Optional<DatabaseDocumentResponse> getOneByProjectId(Integer projectId, Long version) {
|
||||
|
@ -472,48 +268,33 @@ public class DocumentService {
|
|||
if (CollectionUtils.isEmpty(request.getTableIds()) || !projectDao.existsById(projectId)) {
|
||||
return emptyList();
|
||||
}
|
||||
var current = this.getTableDetails(projectId, databaseDocumentId, request.getTableIds());
|
||||
List<Integer> currentTableIds = current.stream().map(t -> t.getId()).collect(Collectors.toList());
|
||||
var missedIds = CollectionUtils.disjunction(currentTableIds, request.getTableIds());
|
||||
|
||||
if (request.getOriginalVersion() != null) {
|
||||
DatabaseDocument doc =
|
||||
databaseDocumentDao.selectOptionalByProjectIdAndVersion(projectId, request.getOriginalVersion())
|
||||
.orElseThrow(DomainErrors.DOCUMENT_VERSION_IS_INVALID::exception);
|
||||
List<String> tableNames = current.stream().map(t -> t.getName()).distinct().collect(Collectors.toList());
|
||||
List<Integer> originalTableIds =
|
||||
tableDocumentDao.selectTableIdsByDatabaseDocumentIdAndTableNameIn(doc.getId(), tableNames);
|
||||
var allOriginalTableIds = CollectionUtils.union(missedIds, originalTableIds);
|
||||
var original = this.getTableDetails(projectId, doc.getId(), allOriginalTableIds);
|
||||
Map<String, TableDocumentResponse> currentMapByName = current.stream()
|
||||
.collect(Collectors.toMap(TableDocumentResponse::getName, Function.identity(), (a, b) -> a));
|
||||
Map<String, TableDocumentResponse> originalMapByName = original.stream()
|
||||
.collect(Collectors.toMap(TableDocumentResponse::getName, Function.identity(), (a, b) -> a));
|
||||
List<TableMeta> currentMeta = databaseMetaConverter.of(current);
|
||||
List<TableMeta> originalMeta = databaseMetaConverter.of(original);
|
||||
List<TableDiffResult> diffs = DocumentDiffs.tableDiff(originalMeta, currentMeta);
|
||||
return diffs.stream()
|
||||
.map(diff -> {
|
||||
if (diff.getDiffType() == DiffType.ADDED) {
|
||||
TableDocumentResponse c = currentMapByName.get(diff.getId());
|
||||
return DiffTypeFills.fillAdded(c, diff);
|
||||
}
|
||||
if (diff.getDiffType() == DiffType.REMOVED) {
|
||||
TableDocumentResponse t = originalMapByName.get(diff.getId());
|
||||
return DiffTypeFills.fillRemoved(t);
|
||||
}
|
||||
if (diff.getDiffType() == DiffType.MODIFIED) {
|
||||
TableDocumentResponse c = currentMapByName.get(diff.getId());
|
||||
TableDocumentResponse o = originalMapByName.get(diff.getId());
|
||||
return DiffTypeFills.fillModified(c, o, diff);
|
||||
}
|
||||
TableDocumentResponse t = currentMapByName.get(diff.getId());
|
||||
t.setDiffType(DiffType.NONE);
|
||||
return t;
|
||||
})
|
||||
List<TableDocDiff> diffResults =
|
||||
this.tableDiffs(projectId, request.getOriginalVersion(), request.getCurrentVersion());
|
||||
// discussion
|
||||
var discussions = documentDiscussionDao.selectAllDiscussionCount(projectId);
|
||||
Map<String, Integer> discussionCountMapByJoinName = discussions.stream()
|
||||
.collect(Collectors.toMap(
|
||||
d -> String.join(".", d.getTableName(), d.getColumnName()),
|
||||
DocumentDiscussionCountPojo::getCount,
|
||||
(a, b) -> a));
|
||||
|
||||
// description
|
||||
var descriptions = documentDescriptionDao.selectByProjectId(projectId);
|
||||
Map<String, String> descriptionMapByJoinName = descriptions.stream()
|
||||
.collect(Collectors.toMap(
|
||||
d -> String.join(".", d.getTableName(), d.getColumnName()),
|
||||
DocumentDescription::getContent,
|
||||
(a, b) -> a));
|
||||
return diffResults.stream()
|
||||
.filter(tableDocDiff -> request.getTableIds().contains(tableDocDiff.getId()))
|
||||
.map(diff -> documentResponseConverter.ofDiff(
|
||||
diff, discussionCountMapByJoinName, descriptionMapByJoinName))
|
||||
.sorted(Comparator.comparing(TableDocumentResponse::getName))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
} else {
|
||||
var current = this.getTableDetails(projectId, databaseDocumentId, request.getTableIds());
|
||||
current.sort(Comparator.comparing(TableDocumentResponse::getName));
|
||||
return current;
|
||||
}
|
||||
|
@ -569,4 +350,15 @@ public class DocumentService {
|
|||
return tableResponseConverter.from(tables, columnMapByTableId);
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseDocDiff retrieveDatabaseDocumentDiff(DatabaseDocument databaseDocument) {
|
||||
Integer docId = databaseDocument.getId();
|
||||
List<TableDocument> tables = tableDocumentDao.selectByDatabaseDocumentId(docId);
|
||||
List<TableColumnDocument> columns = tableColumnDocumentDao.selectByDatabaseDocumentId(docId);
|
||||
List<TableIndexDocument> indexes = tableIndexDocumentDao.selectByDatabaseMetaId(docId);
|
||||
List<TableTriggerDocument> triggers = tableTriggerDocumentDao.selectByDatabaseDocumentId(docId);
|
||||
List<TableForeignKeyDocument> fks = tableForeignKeyDocumentDao.selectByDatabaseDocumentId(docId);
|
||||
return documentDiffConverter.of(databaseDocument, tables, columns, indexes, triggers, fks);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
package com.databasir.core.domain.document.service;
|
||||
|
||||
import com.databasir.common.DatabasirException;
|
||||
import com.databasir.core.Databasir;
|
||||
import com.databasir.core.DatabasirConfig;
|
||||
import com.databasir.core.diff.Diffs;
|
||||
import com.databasir.core.diff.data.DiffType;
|
||||
import com.databasir.core.diff.data.RootDiff;
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import com.databasir.core.domain.document.converter.DatabaseMetaConverter;
|
||||
import com.databasir.core.domain.document.converter.DocumentFullTextConverter;
|
||||
import com.databasir.core.domain.document.converter.DocumentPojoConverter;
|
||||
import com.databasir.core.domain.document.event.DocumentUpdated;
|
||||
import com.databasir.core.infrastructure.connection.DatabaseConnectionService;
|
||||
import com.databasir.core.infrastructure.converter.JsonConverter;
|
||||
import com.databasir.core.infrastructure.event.EventPublisher;
|
||||
import com.databasir.core.meta.data.DatabaseMeta;
|
||||
import com.databasir.dao.impl.*;
|
||||
import com.databasir.dao.tables.pojos.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class DocumentSyncService {
|
||||
|
||||
private final ProjectDao projectDao;
|
||||
|
||||
private final ProjectSyncRuleDao projectSyncRuleDao;
|
||||
|
||||
private final DataSourceDao dataSourceDao;
|
||||
|
||||
private final DataSourcePropertyDao dataSourcePropertyDao;
|
||||
|
||||
private final DatabaseConnectionService databaseConnectionService;
|
||||
|
||||
private final DatabaseDocumentDao databaseDocumentDao;
|
||||
|
||||
private final TableDocumentDao tableDocumentDao;
|
||||
|
||||
private final TableColumnDocumentDao tableColumnDocumentDao;
|
||||
|
||||
private final TableIndexDocumentDao tableIndexDocumentDao;
|
||||
|
||||
private final TableTriggerDocumentDao tableTriggerDocumentDao;
|
||||
|
||||
private final TableForeignKeyDocumentDao tableForeignKeyDocumentDao;
|
||||
|
||||
private final DocumentFullTextDao documentFullTextDao;
|
||||
|
||||
private final DocumentPojoConverter documentPojoConverter;
|
||||
|
||||
private final DocumentFullTextConverter documentFullTextConverter;
|
||||
|
||||
private final JsonConverter jsonConverter;
|
||||
|
||||
private final DatabaseMetaConverter databaseMetaConverter;
|
||||
|
||||
private final EventPublisher eventPublisher;
|
||||
|
||||
@Transactional
|
||||
public void syncByProjectId(Integer projectId) {
|
||||
projectDao.selectOptionalById(projectId)
|
||||
.orElseThrow(DomainErrors.PROJECT_NOT_FOUND::exception);
|
||||
DatabaseMeta current = retrieveDatabaseMeta(projectId);
|
||||
Optional<DatabaseDocument> originalOption = databaseDocumentDao.selectNotArchivedByProjectId(projectId);
|
||||
if (originalOption.isPresent()) {
|
||||
DatabaseDocument original = originalOption.get();
|
||||
DatabaseMeta originalMeta = retrieveOriginalDatabaseMeta(original);
|
||||
RootDiff diff = Diffs.diff(originalMeta, current);
|
||||
if (diff.getDiffType() == DiffType.NONE) {
|
||||
log.info("ignore project {} {} sync data, because without change",
|
||||
projectId,
|
||||
original.getDatabaseName());
|
||||
return;
|
||||
}
|
||||
Integer previousDocumentId = original.getId();
|
||||
// archive old version
|
||||
databaseDocumentDao.updateIsArchiveById(previousDocumentId, true);
|
||||
Long version = original.getVersion();
|
||||
Integer docId = saveNewDocument(current, version + 1, original.getProjectId());
|
||||
eventPublisher.publish(new DocumentUpdated(diff, version + 1, version, projectId, docId));
|
||||
} else {
|
||||
Integer docId = saveNewDocument(current, 1L, projectId);
|
||||
RootDiff diff = null;
|
||||
try {
|
||||
diff = Diffs.diff(null, current);
|
||||
} catch (Exception e) {
|
||||
log.error("diff project " + projectId + " error, fallback diff type to NONE", e);
|
||||
}
|
||||
eventPublisher.publish(new DocumentUpdated(diff, 1L, null, projectId, docId));
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseMeta retrieveDatabaseMeta(Integer projectId) {
|
||||
ProjectSyncRule rule = projectSyncRuleDao.selectByProjectId(projectId);
|
||||
DataSource dataSource = dataSourceDao.selectByProjectId(projectId);
|
||||
List<DataSourceProperty> properties = dataSourcePropertyDao.selectByDataSourceId(dataSource.getId());
|
||||
Connection jdbcConnection = databaseConnectionService.create(dataSource, properties);
|
||||
DatabasirConfig databasirConfig = new DatabasirConfig();
|
||||
databasirConfig.setIgnoreTableNameRegex(jsonConverter.fromJson(rule.getIgnoreTableNameRegexArray()));
|
||||
databasirConfig.setIgnoreTableColumnNameRegex(jsonConverter.fromJson(rule.getIgnoreColumnNameRegexArray()));
|
||||
try {
|
||||
if (jdbcConnection == null) {
|
||||
throw DomainErrors.CONNECT_DATABASE_FAILED.exception();
|
||||
}
|
||||
DatabaseMeta databaseMeta = Databasir.of(databasirConfig)
|
||||
.get(jdbcConnection, dataSource.getDatabaseName(), dataSource.getSchemaName())
|
||||
.orElseThrow(DomainErrors.DATABASE_META_NOT_FOUND::exception);
|
||||
return databaseMeta;
|
||||
} finally {
|
||||
try {
|
||||
if (jdbcConnection != null && !jdbcConnection.isClosed()) {
|
||||
jdbcConnection.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("close jdbc connection error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseMeta retrieveOriginalDatabaseMeta(DatabaseDocument original) {
|
||||
Integer docId = original.getId();
|
||||
List<TableDocument> tables = tableDocumentDao.selectByDatabaseDocumentId(docId);
|
||||
List<TableColumnDocument> columns = tableColumnDocumentDao.selectByDatabaseDocumentId(docId);
|
||||
List<TableIndexDocument> indexes = tableIndexDocumentDao.selectByDatabaseMetaId(docId);
|
||||
List<TableTriggerDocument> triggers = tableTriggerDocumentDao.selectByDatabaseDocumentId(docId);
|
||||
List<TableForeignKeyDocument> fks = tableForeignKeyDocumentDao.selectByDatabaseDocumentId(docId);
|
||||
return databaseMetaConverter.of(original, tables, columns, indexes, triggers, fks);
|
||||
}
|
||||
|
||||
private Integer saveNewDocument(DatabaseMeta meta,
|
||||
Long version,
|
||||
Integer projectId) {
|
||||
|
||||
var dbDocPojo = documentPojoConverter.toDatabasePojo(projectId, meta, version);
|
||||
final Integer docId;
|
||||
try {
|
||||
docId = databaseDocumentDao.insertAndReturnId(dbDocPojo);
|
||||
dbDocPojo.setId(docId);
|
||||
} catch (DuplicateKeyException e) {
|
||||
log.warn("ignore insert database document projectId={} version={}", projectId, version);
|
||||
throw new DatabasirException(DomainErrors.DATABASE_DOCUMENT_DUPLICATE_KEY);
|
||||
}
|
||||
meta.getTables().forEach(table -> {
|
||||
TableDocument tableMeta =
|
||||
documentPojoConverter.toTablePojo(docId, table);
|
||||
Integer tableMetaId = tableDocumentDao.insertAndReturnId(tableMeta);
|
||||
tableMeta.setId(tableMetaId);
|
||||
// column
|
||||
var columns = documentPojoConverter.toColumnPojo(docId, tableMetaId, table.getColumns());
|
||||
tableColumnDocumentDao.batchInsert(columns);
|
||||
// index
|
||||
var indexes = documentPojoConverter.toIndexPojo(docId, tableMetaId, table.getIndexes())
|
||||
.stream()
|
||||
.filter(index -> {
|
||||
if (index.getName() != null) {
|
||||
return true;
|
||||
} else {
|
||||
log.warn("ignore table {} index {}, cause name is null", table.getName(), index);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
tableIndexDocumentDao.batchInsert(indexes);
|
||||
// foreign key
|
||||
var foreignKeys = documentPojoConverter.toForeignKeyPojo(docId, tableMetaId, table.getForeignKeys());
|
||||
tableForeignKeyDocumentDao.batchInsert(foreignKeys);
|
||||
|
||||
// trigger
|
||||
var triggers = documentPojoConverter.toTriggerPojo(docId, tableMetaId, table.getTriggers());
|
||||
tableTriggerDocumentDao.batchInsert(triggers);
|
||||
});
|
||||
log.info("save new version document success: projectId = {}, name = {}, version = {}",
|
||||
projectId, meta.getDatabaseName(), version);
|
||||
return docId;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package com.databasir.core.domain.document.diff;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class BaseTypeFieldEqualFunctionTest {
|
||||
|
||||
@Test
|
||||
public void testApplyAllNull() {
|
||||
BaseTypeFieldEqualFunction equalFunction = new BaseTypeFieldEqualFunction(List.of());
|
||||
|
||||
// Setup
|
||||
final Object that = null;
|
||||
final Object other = null;
|
||||
|
||||
// Run the test
|
||||
final Boolean result = equalFunction.apply(that, other);
|
||||
|
||||
// Verify the results
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplySimpleObject() {
|
||||
SimpleObject that = SimpleObject.builder()
|
||||
.id(1)
|
||||
.height(180L)
|
||||
.name("test")
|
||||
.build();
|
||||
|
||||
SimpleObject other = SimpleObject.builder()
|
||||
.id(1)
|
||||
.height(180L)
|
||||
.name("test")
|
||||
.build();
|
||||
|
||||
BaseTypeFieldEqualFunction equalFunction = new BaseTypeFieldEqualFunction(List.of());
|
||||
final Boolean result = equalFunction.apply(that, other);
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyComplexObject() {
|
||||
SimpleObject thatItem = SimpleObject.builder()
|
||||
.id(1)
|
||||
.height(180L)
|
||||
.name("test")
|
||||
.build();
|
||||
ComplexObject thatObj = new ComplexObject();
|
||||
thatObj.setId(1);
|
||||
thatObj.setName("eq");
|
||||
thatObj.setItems(List.of(thatItem));
|
||||
|
||||
SimpleObject otherItem = SimpleObject.builder()
|
||||
.id(1)
|
||||
.height(180L)
|
||||
.name("test")
|
||||
.build();
|
||||
ComplexObject otherObj = new ComplexObject();
|
||||
otherObj.setId(1);
|
||||
otherObj.setName("eq");
|
||||
otherObj.setItems(List.of(otherItem));
|
||||
|
||||
BaseTypeFieldEqualFunction equalFunction = new BaseTypeFieldEqualFunction(List.of());
|
||||
final Boolean result = equalFunction.apply(thatObj, otherObj);
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyComplexObject2() {
|
||||
SimpleObject thatItem = SimpleObject.builder()
|
||||
.id(1)
|
||||
.height(180L)
|
||||
.name("test")
|
||||
.build();
|
||||
ComplexObject thatObj = new ComplexObject();
|
||||
thatObj.setId(1);
|
||||
thatObj.setName("eq");
|
||||
thatObj.setItems(List.of(thatItem));
|
||||
|
||||
ComplexObject otherObj = new ComplexObject();
|
||||
otherObj.setId(1);
|
||||
otherObj.setName("eq");
|
||||
otherObj.setItems(List.of());
|
||||
|
||||
BaseTypeFieldEqualFunction equalFunction = new BaseTypeFieldEqualFunction(List.of());
|
||||
final Boolean result = equalFunction.apply(thatObj, otherObj);
|
||||
assertFalse(result);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.databasir.core.domain.document.diff;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ComplexObject {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
private List<SimpleObject> items = new ArrayList<>();
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.databasir.core.domain.document.diff;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class SimpleObject {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private Long height;
|
||||
|
||||
private String name;
|
||||
|
||||
}
|
Loading…
Reference in New Issue