package com.taobao.yugong.common.db;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.taobao.yugong.common.db.meta.ColumnValue;
import com.taobao.yugong.common.model.record.IncrementOpType;
import com.taobao.yugong.common.model.record.IncrementRecord;
/**
* <pre>
* pk相同的多条变更数据合并后的结果是:
* 1, I
* 2, U
* 3, D
* 如果有一条I,多条U,merge成I;
* 如果有多条U,取最晚的那条;
* </pre>
*
* @author agapple 2012-10-31 下午05:23:40
*/
public class IncrementRecordMerger {
/**
* 将一批数据进行根据table+主键信息进行合并,保证一个表的一个pk记录只有一条结果
*
* @param records
* @return
*/
public static List<IncrementRecord> merge(List<IncrementRecord> records) {
Map<RowKey, IncrementRecord> result = new LinkedHashMap<RowKey, IncrementRecord>();
for (IncrementRecord record : records) {
merge(record, result);
}
return new LinkedList<IncrementRecord>(result.values());
}
public static void merge(IncrementRecord record, Map<RowKey, IncrementRecord> result) {
IncrementOpType opType = record.getOpType();
switch (opType) {
case I:
mergeInsert(record, result);
break;
case U:
mergeUpdate(record, result);
break;
case D:
mergeDelete(record, result);
break;
default:
break;
}
}
private static void mergeInsert(IncrementRecord record, Map<RowKey, IncrementRecord> result) {
// insert无主键变更的处理
RowKey rowKey = new RowKey(record.getSchemaName(), record.getTableName(), record.getPrimaryKeys());
if (!result.containsKey(rowKey)) {
result.put(rowKey, record);
} else {
IncrementRecord oldrecord = result.get(rowKey);
// 如果上一条变更是delete的,就直接用insert替换
if (oldrecord.getOpType() == IncrementOpType.D) {
result.put(rowKey, record);
} else if (oldrecord.getOpType() == IncrementOpType.U || oldrecord.getOpType() == IncrementOpType.I) {
// 因为物化视图无序,会出现update-insert/insert-insert happend
// logger.warn("update-insert/insert-insert happend. before[{}] , after[{}]",
// oldrecord, record);
// 如果上一条变更是update的,就用insert替换,并且把上一条存在而这一条不存在的字段值拷贝到这一条中
IncrementRecord mergerecord = replaceColumnValue(record, oldrecord);
result.put(rowKey, mergerecord);
}
}
}
private static void mergeUpdate(IncrementRecord record, Map<RowKey, IncrementRecord> result) {
RowKey rowKey = new RowKey(record.getSchemaName(), record.getTableName(), record.getPrimaryKeys());
if (!result.containsKey(rowKey)) {// 没有主键变更
result.put(rowKey, record);
} else {
IncrementRecord oldrecord = result.get(rowKey);
// 如果上一条变更是insert的,就把这一条的eventType改成insert,并且把上一条存在而这一条不存在的字段值拷贝到这一条中
if (oldrecord.getOpType() == IncrementOpType.I) {
record.setOpType(IncrementOpType.I);
IncrementRecord mergeRecord = replaceColumnValue(record, oldrecord);
result.put(rowKey, mergeRecord);
} else if (oldrecord.getOpType() == IncrementOpType.U) {// 可能存在
// 1->2
// ,
// 2update的问题
// 如果上一条变更是update的,把上一条存在而这一条不存在的数据拷贝到这一条中
IncrementRecord mergeRecord = replaceColumnValue(record, oldrecord);
result.put(rowKey, mergeRecord);
} else if (oldrecord.getOpType() == IncrementOpType.D) {
// 异常情况,出现 delete + update,那就直接更新为update
result.put(rowKey, record);
}
}
}
private static void mergeDelete(IncrementRecord record, Map<RowKey, IncrementRecord> result) {
// 只保留pks,把columns去掉. 以后针对数据仓库可以开放delete columns记录
RowKey rowKey = new RowKey(record.getSchemaName(), record.getTableName(), record.getPrimaryKeys());
result.put(rowKey, record);
}
/**
* 把old中的值存在而new中不存在的值合并到new中,并且把old中的变更前的主键保存到new中的变更前的主键.
*
* @param newrecord
* @param oldrecord
* @return
*/
private static IncrementRecord replaceColumnValue(IncrementRecord newrecord, IncrementRecord oldrecord) {
List<ColumnValue> newColumns = newrecord.getColumns();
List<ColumnValue> oldColumns = oldrecord.getColumns();
List<ColumnValue> temp = new ArrayList<ColumnValue>();
for (ColumnValue oldColumn : oldColumns) {
boolean contain = false;
for (ColumnValue newColumn : newColumns) {
if (oldColumn.getColumn().getName().equalsIgnoreCase(newColumn.getColumn().getName())) {
contain = true;
}
}
if (!contain) {
temp.add(oldColumn);
}
}
newColumns.addAll(temp);
// 把上一次变更的旧主键传递到这次变更的旧主键.
return newrecord;
}
public static class RowKey implements Serializable {
private static final long serialVersionUID = -7369951798499581038L;
private String schemaName; // tableId代表统配符时,需要指定schemaName
private String tableName; // tableId代表统配符时,需要指定tableName
private List<ColumnValue> keys = new ArrayList<ColumnValue>();
public RowKey(String schemaName, String tableName, List<ColumnValue> keys){
this.schemaName = schemaName;
this.tableName = tableName;
this.keys = keys;
}
public RowKey(List<ColumnValue> keys){
this.keys = keys;
}
public List<ColumnValue> getKeys() {
return keys;
}
public void setKeys(List<ColumnValue> keys) {
this.keys = keys;
}
public String getSchemaName() {
return schemaName;
}
public void setSchemaName(String schemaName) {
this.schemaName = schemaName;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((keys == null) ? 0 : keys.hashCode());
result = prime * result + ((schemaName == null) ? 0 : schemaName.hashCode());
result = prime * result + ((tableName == null) ? 0 : tableName.hashCode());
return result;
}
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
RowKey other = (RowKey) obj;
if (keys == null) {
if (other.keys != null) return false;
} else if (!keys.equals(other.keys)) return false;
if (schemaName == null) {
if (other.schemaName != null) return false;
} else if (!schemaName.equals(other.schemaName)) return false;
if (tableName == null) {
if (other.tableName != null) return false;
} else if (!tableName.equals(other.tableName)) return false;
return true;
}
}
}