-
Notifications
You must be signed in to change notification settings - Fork 8.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Do data validation when undo. #1060
Merged
Merged
Changes from 6 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
8ee4266
Do data validation when undo.
ujjboy d004c1e
Merge branch 'develop' into undo_check
leizhiyuan d2092fd
Merge remote-tracking branch 'upstream/develop' into undo_check
ujjboy 3a8631f
Run test case use h2 database.
ujjboy 5ff3465
Merge remote-tracking branch 'origin/undo_check' into undo_check
ujjboy 128369c
Merge branch 'develop' into undo_check
lovepoem 9d00eb5
fix as review.
ujjboy a561bdf
Merge branch 'develop' into undo_check
ujjboy 9c1d120
Add log.
ujjboy 766d3ca
Merge branch 'develop' into undo_check
lovepoem 108afe8
Merge branch 'develop' into undo_check
ujjboy 18ea10c
Merge branch 'develop' into undo_check
leizhiyuan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,16 +15,23 @@ | |
*/ | ||
package io.seata.rm.datasource.undo; | ||
|
||
import java.sql.Connection; | ||
import java.sql.PreparedStatement; | ||
import java.sql.SQLException; | ||
import java.util.ArrayList; | ||
|
||
import com.alibaba.fastjson.JSON; | ||
import io.seata.common.util.StringUtils; | ||
import io.seata.rm.datasource.DataCompareUtils; | ||
import io.seata.rm.datasource.sql.struct.Field; | ||
import io.seata.rm.datasource.sql.struct.KeyType; | ||
import io.seata.rm.datasource.sql.struct.Row; | ||
import io.seata.rm.datasource.sql.struct.TableMeta; | ||
import io.seata.rm.datasource.sql.struct.TableRecords; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.sql.Connection; | ||
import java.sql.PreparedStatement; | ||
import java.sql.ResultSet; | ||
import java.sql.SQLException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
/** | ||
* The type Abstract undo executor. | ||
|
@@ -34,6 +41,18 @@ | |
*/ | ||
public abstract class AbstractUndoExecutor { | ||
|
||
/** | ||
* Logger for AbstractUndoExecutor | ||
**/ | ||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractUndoExecutor.class); | ||
|
||
/** | ||
* template of check sql | ||
* | ||
* TODO support multiple primary key | ||
*/ | ||
private static final String CHECK_SQL_TEMPLATE = "SELECT * FROM %s WHERE %s in (%s)"; | ||
|
||
/** | ||
* The Sql undo log. | ||
*/ | ||
|
@@ -72,11 +91,11 @@ public SQLUndoLog getSqlUndoLog() { | |
*/ | ||
public void executeOn(Connection conn) throws SQLException { | ||
|
||
// no need undo if the before data snapshot is equivalent to the after data snapshot. | ||
if (DataCompareUtils.isRecordsEquals(sqlUndoLog.getBeforeImage(), sqlUndoLog.getAfterImage())) { | ||
// when the data validation is not ok | ||
if (!dataValidation(conn)) { | ||
return; | ||
} | ||
dataValidation(conn); | ||
|
||
try { | ||
String undoSQL = buildUndoSQL(); | ||
|
||
|
@@ -145,9 +164,115 @@ protected void undoPrepare(PreparedStatement undoPST, ArrayList<Field> undoValue | |
* Data validation. | ||
* | ||
* @param conn the conn | ||
* @throws SQLException the sql exception | ||
* @return return true if data validation is ok and need continue | ||
* @throws SQLException the sql exception such as has dirty data | ||
*/ | ||
protected void dataValidation(Connection conn) throws SQLException { | ||
protected boolean dataValidation(Connection conn) throws SQLException { | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It ‘s better to add a switcher to close the dirty data check manually, the switcher is open default. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I will add it in the next standalone PR. |
||
TableRecords beforeRecords = sqlUndoLog.getBeforeImage(); | ||
TableRecords afterRecords = sqlUndoLog.getAfterImage(); | ||
|
||
// Compare current data with before data | ||
// No need undo if the before data snapshot is equivalent to the after data snapshot. | ||
if (DataCompareUtils.isRecordsEquals(beforeRecords, afterRecords)) { | ||
return false; | ||
} | ||
|
||
// Validate if data is dirty. | ||
TableRecords currentRecords = queryCurrentRecords(conn); | ||
// compare with current data and after image. | ||
if (!DataCompareUtils.isRecordsEquals(afterRecords, currentRecords)) { | ||
|
||
// If current data is not equivalent to the after data, then compare the current data with the before | ||
// data, too. No need continue to undo if current data is equivalent to the before data snapshot | ||
if (DataCompareUtils.isRecordsEquals(beforeRecords, currentRecords)) { | ||
return false; | ||
} else { | ||
if (LOGGER.isDebugEnabled()) { | ||
LOGGER.debug("check dirty datas failed, old and new data are not equal," + | ||
"tableName:[" + sqlUndoLog.getTableName() + "]," + | ||
"oldRows:[" + JSON.toJSONString(afterRecords.getRows()) + "]," + | ||
"newRows:[" + JSON.toJSONString(currentRecords.getRows()) + "]."); | ||
} | ||
throw new SQLException("Has dirty records when undo."); | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
/** | ||
* Query current records. | ||
* | ||
* @param conn the conn | ||
* @return the table records | ||
* @throws SQLException the sql exception | ||
*/ | ||
protected TableRecords queryCurrentRecords(Connection conn) throws SQLException { | ||
TableRecords undoRecords = getUndoRows(); | ||
TableMeta tableMeta = undoRecords.getTableMeta(); | ||
String pkName = tableMeta.getPkName(); | ||
int pkType = tableMeta.getColumnMeta(pkName).getDataType(); | ||
|
||
// pares pk values | ||
Object[] pkValues = parsePkValues(getUndoRows()); | ||
if (pkValues.length == 0) { | ||
return TableRecords.empty(tableMeta); | ||
} | ||
StringBuffer replace = new StringBuffer(); | ||
for (int i = 0; i < pkValues.length; i++) { | ||
replace.append("?,"); | ||
} | ||
// build check sql | ||
String checkSQL = String.format(CHECK_SQL_TEMPLATE, sqlUndoLog.getTableName(), pkName, | ||
replace.substring(0, replace.length() - 1)); | ||
|
||
PreparedStatement statement = null; | ||
ResultSet checkSet = null; | ||
TableRecords currentRecords; | ||
try { | ||
statement = conn.prepareStatement(checkSQL); | ||
for (int i = 1; i <= pkValues.length; i++) { | ||
statement.setObject(i, pkValues[i - 1], pkType); | ||
} | ||
checkSet = statement.executeQuery(); | ||
currentRecords = TableRecords.buildRecords(tableMeta, checkSet); | ||
} finally { | ||
if (checkSet != null) { | ||
try { | ||
checkSet.close(); | ||
} catch (Exception e) { | ||
} | ||
} | ||
if (statement != null) { | ||
try { | ||
statement.close(); | ||
} catch (Exception e) { | ||
} | ||
} | ||
} | ||
return currentRecords; | ||
} | ||
|
||
/** | ||
* Parse pk values object [ ]. | ||
* | ||
* @param records the records | ||
* @return the object [ ] | ||
*/ | ||
protected Object[] parsePkValues(TableRecords records) { | ||
String pkName = records.getTableMeta().getPkName(); | ||
List<Row> undoRows = records.getRows(); | ||
Object[] pkValues = new Object[undoRows.size()]; | ||
for (int i = 0; i < undoRows.size(); i++) { | ||
List<Field> fields = undoRows.get(i).getFields(); | ||
if (fields != null) { | ||
for (Field field : fields) { | ||
if (StringUtils.equalsIgnoreCase(pkName, field.getName())) { | ||
pkValues[i] = field.getValue(); | ||
} | ||
} | ||
} | ||
} | ||
return pkValues; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we throw some exception explicitly for the code change later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I don’t get your point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I add some log and change the method name for more explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as communicated offline
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@leizhiyuan done.