package com.taobao.yugong.applier;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCallback;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.MigrateMap;
import com.taobao.yugong.common.db.RecordDiffer;
import com.taobao.yugong.common.db.meta.ColumnMeta;
import com.taobao.yugong.common.db.meta.ColumnValue;
import com.taobao.yugong.common.db.meta.Table;
import com.taobao.yugong.common.db.meta.TableMetaGenerator;
import com.taobao.yugong.common.db.sql.SqlTemplates;
import com.taobao.yugong.common.model.DbType;
import com.taobao.yugong.common.model.YuGongContext;
import com.taobao.yugong.common.model.record.Record;
import com.taobao.yugong.common.utils.YuGongUtils;
import com.taobao.yugong.exception.YuGongException;
/**
* 增加数据对比
*
* @author agapple 2013-9-29 下午1:14:03
*/
public class CheckRecordApplier extends AbstractRecordApplier {
protected static final Logger logger = LoggerFactory.getLogger(CheckRecordApplier.class);
protected Map<List<String>, TableSqlUnit> selectSqlCache;
protected Map<List<String>, Table> tableCache;
protected YuGongContext context;
protected DbType dbType;
public CheckRecordApplier(YuGongContext context){
this.context = context;
}
public void start() {
super.start();
dbType = YuGongUtils.judgeDbType(context.getTargetDs());
tableCache = MigrateMap.makeComputingMap(new Function<List<String>, Table>() {
public Table apply(List<String> names) {
if (names.size() != 2) {
throw new YuGongException("names[" + names.toString() + "] is not valid");
}
return TableMetaGenerator.getTableMeta(context.getTargetDs(),
context.isIgnoreSchema() ? null : names.get(0),
names.get(1));
}
});
selectSqlCache = MigrateMap.makeMap();
}
public void stop() {
super.stop();
}
public void apply(List<Record> records) throws YuGongException {
// no one,just return
if (YuGongUtils.isEmpty(records)) {
return;
}
doApply(records);
}
protected void doApply(List<Record> records) {
Map<List<String>, List<Record>> buckets = MigrateMap.makeComputingMap(new Function<List<String>, List<Record>>() {
public List<Record> apply(List<String> names) {
return Lists.newArrayList();
}
});
// 根据目标库的不同,划分为多个bucket
for (Record record : records) {
buckets.get(Arrays.asList(record.getSchemaName(), record.getTableName())).add(record);
}
JdbcTemplate jdbcTemplate = new JdbcTemplate(context.getTargetDs());
for (final List<Record> batchRecords : buckets.values()) {
List<Record> queryRecords = null;
if (context.isBatchApply()) {
queryRecords = queryByBatch(jdbcTemplate, batchRecords);
} else {
queryRecords = queryOneByOne(jdbcTemplate, batchRecords);
}
diff(batchRecords, queryRecords);
}
}
protected List<Record> queryByBatch(JdbcTemplate jdbcTemplate, final List<Record> batchRecords) {
TableSqlUnit sqlUnit = getSqlUnit(batchRecords.get(0));
final String schemaName = batchRecords.get(0).getSchemaName();
final String tableName = batchRecords.get(0).getTableName();
final Map<String, Integer> indexs = sqlUnit.applierIndexs;
final List<ColumnMeta> primaryKeys = getPrimaryMetas(batchRecords.get(0));
final List<ColumnMeta> columns = getColumnMetas(batchRecords.get(0));
Table meta = TableMetaGenerator.getTableMeta(context.getTargetDs(),
context.isIgnoreSchema() ? null : batchRecords.get(0).getSchemaName(),
batchRecords.get(0).getTableName());
String selectSql = null;
if (dbType == DbType.MYSQL) {
selectSql = SqlTemplates.MYSQL.getSelectInSql(meta.getSchema(),
meta.getName(),
YuGongUtils.getColumnNameArray(primaryKeys),
YuGongUtils.getColumnNameArray(columns),
batchRecords.size());
} else if (dbType == DbType.ORACLE) {
selectSql = SqlTemplates.ORACLE.getSelectInSql(meta.getSchema(),
meta.getName(),
YuGongUtils.getColumnNameArray(primaryKeys),
YuGongUtils.getColumnNameArray(columns),
batchRecords.size());
}
return (List<Record>) jdbcTemplate.execute(selectSql, new PreparedStatementCallback() {
public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
// 批量查询,根据pks in 语法
int size = batchRecords.get(0).getPrimaryKeys().size();
int i = 0;
for (Record record : batchRecords) {
int count = 0;
for (ColumnValue pk : record.getPrimaryKeys()) {
// 源库和目标的库主键信息可能不一致
Integer index = getIndex(indexs, pk, true);
if (index != null) {
ps.setObject(size * i + index, pk.getValue(), pk.getColumn().getType());
count++;
}
}
for (ColumnValue col : record.getColumns()) {
// 源库和目标的库主键信息可能不一致
Integer index = getIndex(indexs, col, true);
if (index != null) {
ps.setObject(size * i + index, col.getValue(), col.getColumn().getType());
count++;
}
}
if (count != indexs.size()) {
processMissColumn(record, indexs);
}
i++;
}
List<Record> result = Lists.newArrayList();
ResultSet rs = ps.executeQuery();
while (rs.next()) {
List<ColumnValue> cms = new ArrayList<ColumnValue>();
List<ColumnValue> pks = new ArrayList<ColumnValue>();
// 需要和源库转义后的record保持相同的primary/column顺序,否则对比会失败
for (ColumnMeta pk : primaryKeys) {
ColumnValue cv = getColumnValue(rs, getTargetEncoding(), pk);
pks.add(cv);
}
for (ColumnMeta col : columns) {
ColumnValue cv = getColumnValue(rs, getTargetEncoding(), col);
cms.add(cv);
}
Record re = new Record(schemaName, tableName, pks, cms);
result.add(re);
}
return result;
}
});
}
/**
* 一条条记录串行处理
*/
protected List<Record> queryOneByOne(JdbcTemplate jdbcTemplate, final List<Record> records) {
TableSqlUnit sqlUnit = getSqlUnit(records.get(0));
String selectSql = sqlUnit.applierSql;
final Map<String, Integer> indexs = sqlUnit.applierIndexs;
final List<ColumnMeta> primaryKeys = getPrimaryMetas(records.get(0));
final List<ColumnMeta> columns = getColumnMetas(records.get(0));
return (List<Record>) jdbcTemplate.execute(selectSql, new PreparedStatementCallback() {
public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
List<Record> result = Lists.newArrayList();
for (Record record : records) {
int count = 0;
for (ColumnValue pk : record.getPrimaryKeys()) {
// 源库和目标的库主键信息可能不一致
Integer index = getIndex(indexs, pk, true);
if (index != null) {
ps.setObject(index, pk.getValue(), pk.getColumn().getType());
count++;
}
}
for (ColumnValue col : record.getColumns()) {
// 源库和目标的库主键信息可能不一致
Integer index = getIndex(indexs, col, true);
if (index != null) {
ps.setObject(index, col.getValue(), col.getColumn().getType());
count++;
}
}
if (count != indexs.size()) {
processMissColumn(record, indexs);
}
ResultSet rs = ps.executeQuery();
while (rs.next()) {
List<ColumnValue> cms = new ArrayList<ColumnValue>();
List<ColumnValue> pks = new ArrayList<ColumnValue>();
// 需要和源库转义后的record保持相同的primary/column顺序,否则对比会失败
for (ColumnMeta pk : primaryKeys) {
ColumnValue cv = getColumnValue(rs, getTargetEncoding(), pk);
pks.add(cv);
}
for (ColumnMeta col : columns) {
ColumnValue cv = getColumnValue(rs, getTargetEncoding(), col);
cms.add(cv);
}
Record re = new Record(record.getSchemaName(), record.getTableName(), pks, cms);
result.add(re);
}
}
return result;
}
});
}
protected String getTargetEncoding() {
if (dbType.isOracle()) {
return context.getTargetEncoding();
} else {
return null;
}
}
protected ColumnValue getColumnValue(ResultSet rs, String encoding, ColumnMeta col) throws SQLException {
Object value = null;
if (col.getType() == Types.DATE) {
value = rs.getTimestamp(col.getName());
col = new ColumnMeta(col.getName(), Types.TIMESTAMP);
} else if (col.getType() == Types.TIMESTAMP) {
value = rs.getTimestamp(col.getName());
col = new ColumnMeta(col.getName(), Types.TIMESTAMP);
} else if (YuGongUtils.isCharType(col.getType())) {
// byte[] bytes = rs.getBytes(col.getName());
// if (bytes == null) {
// value = rs.getObject(col.getName());
// } else {
// try {
// value = new String(bytes, encoding);
// } catch (UnsupportedEncodingException e) {
// throw new YuGongException("codec error!!", e);
// }
// }
value = rs.getString(col.getName());
} else if (YuGongUtils.isClobType(col.getType())) {
// Clob c = rs.getClob(col.getName());
// if (c == null) {
// value = rs.getObject(col.getName());
// } else {
// InputStream is = c.getAsciiStream();
// byte[] bb = new byte[(int) c.length()];
// try {
// is.read(bb);
// } catch (IOException e) {
// throw new SQLException("read from clob error,column:" +
// col.getName(), e);
// }
//
// try {
// value = new String(bb, encoding);
// } catch (UnsupportedEncodingException e) {
// throw new RuntimeException("codec error!!", e);
// }
// }
value = rs.getString(col.getName());
} else if (YuGongUtils.isBlobType(col.getType())) {
value = rs.getBytes(col.getName());
} else {
value = rs.getObject(col.getName());
}
return new ColumnValue(col, value);
}
/**
* @param records1 源库的数据
* @param records2 目标库的数据
*/
protected void diff(List<Record> records1, List<Record> records2) {
Map<List<String>, Record> recordMap2 = new HashMap<List<String>, Record>();
for (Record record : records2) {
List<String> objs = Lists.newArrayList();
for (ColumnValue pk : record.getPrimaryKeys()) {
objs.add(ObjectUtils.toString(pk.getValue()));
}
recordMap2.put(objs, record);
}
// 以records1为准
for (Record record : records1) {
List<String> objs = Lists.newArrayList();
for (ColumnValue pk : record.getPrimaryKeys()) {
objs.add(ObjectUtils.toString(pk.getValue()));
}
RecordDiffer.diff(record, recordMap2.remove(objs));
}
// 比对record2多余的数据
for (Record record2 : recordMap2.values()) {
RecordDiffer.diff(null, record2);
}
}
protected TableSqlUnit getSqlUnit(Record record) {
List<String> names = Arrays.asList(record.getSchemaName(), record.getTableName());
TableSqlUnit sqlUnit = selectSqlCache.get(names);
if (sqlUnit == null) {
synchronized (names) {
sqlUnit = selectSqlCache.get(names);
if (sqlUnit == null) { // double-check
sqlUnit = new TableSqlUnit();
String applierSql = null;
Table meta = TableMetaGenerator.getTableMeta(context.getTargetDs(),
context.isIgnoreSchema() ? null : names.get(0),
names.get(1));
String[] primaryKeys = getPrimaryNames(record);
String[] columns = getColumnNames(record);
if (dbType == DbType.MYSQL) {
applierSql = SqlTemplates.MYSQL.getSelectSql(meta.getSchema(),
meta.getName(),
primaryKeys,
columns);
} else if (dbType == DbType.ORACLE) {
applierSql = SqlTemplates.ORACLE.getSelectSql(meta.getSchema(),
meta.getName(),
primaryKeys,
columns);
}
int index = 1;
Map<String, Integer> indexs = new HashMap<String, Integer>();
for (String column : primaryKeys) {
indexs.put(column, index);
index++;
}
if (index == 1) { // 没有主键
for (String column : columns) {
indexs.put(column, index);
index++;
}
}
// 检查下是否少了列
checkColumns(meta, indexs);
sqlUnit.applierSql = applierSql;
sqlUnit.applierIndexs = indexs;
selectSqlCache.put(names, sqlUnit);
}
}
}
return sqlUnit;
}
protected void processMissColumn(final Record record, final Map<String, Integer> indexs) {
// 如果数量不同,则认为缺少主键
List<String> allNames = new ArrayList<String>(indexs.keySet());
for (ColumnValue cv : record.getColumns()) {
Integer index = getIndex(indexs, cv, true);
if (index != null) {
allNames.remove(cv.getColumn().getName());
}
}
for (ColumnValue pk : record.getPrimaryKeys()) {
Integer index = getIndex(indexs, pk, true);
if (index != null) {
allNames.remove(pk.getColumn().getName());
}
}
throw new YuGongException("miss columns" + allNames + " and failed Record Data : " + record.toString());
}
}