optimize: redesign table diff method (#227)

This commit is contained in:
vran 2022-06-07 22:38:09 +08:00 committed by GitHub
parent ebfe669a01
commit 1dc0e10210
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1048 additions and 607 deletions

View File

@ -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) {

View File

@ -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}"

View File

@ -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;
}

View File

@ -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());
}
}

View File

@ -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();
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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<>();
}

View File

@ -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;
}