IDEMPIERE-6150 NON-DB attachments are disappearing when a DB deletion fails (#2377)

* IDEMPIERE-6150 NON-DB attachments are disappearing when a DB deletion fails

- delete AD_Attachment and AD_Archive after commit of deletion Trx

* - add patch from Heng Sin to convert string concatenation to text block

---------

Co-authored-by: hengsin <hengsin@gmail.com>
This commit is contained in:
Carlos Ruiz 2024-05-28 16:24:22 +02:00
parent 9740f2bf18
commit 69995b178b
2 changed files with 65 additions and 25 deletions

View File

@ -4078,14 +4078,14 @@ public abstract class PO
if (m_KeyColumns != null && m_KeyColumns.length == 1 && !getTable().isUUIDKeyTable()) {
//delete cascade only for single key column record
PO_Record.deleteModelCascade(p_info.getTableName(), Record_ID, localTrxName);
// Delete Cascade AD_Table_ID/Record_ID (Attachments, ..)
PO_Record.deleteRecordCascade(AD_Table_ID, Record_ID, localTrxName);
// Delete Cascade AD_Table_ID/Record_ID except Attachments/Archive (that's postponed until trx commit)
PO_Record.deleteRecordCascade(AD_Table_ID, Record_ID, "AD_Table.TableName NOT IN ('AD_Attachment','AD_Archive')", localTrxName);
// Set referencing Record_ID Null AD_Table_ID/Record_ID
PO_Record.setRecordNull(AD_Table_ID, Record_ID, localTrxName);
}
if (Record_UU != null) {
PO_Record.deleteModelCascade(p_info.getTableName(), Record_UU, localTrxName);
PO_Record.deleteRecordCascade(AD_Table_ID, Record_UU, localTrxName);
PO_Record.deleteRecordCascade(AD_Table_ID, Record_UU, "AD_Table.TableName NOT IN ('AD_Attachment','AD_Archive')", localTrxName);
PO_Record.setRecordNull(AD_Table_ID, Record_UU, localTrxName);
}
@ -4226,9 +4226,10 @@ public abstract class PO
}
else
{
if (CacheMgt.get().hasCache(p_info.getTableName())) {
Trx trxdel = Trx.get(get_TrxName(), false);
if (trxdel != null) {
// Schedule the reset cache for after committed the delete
if (CacheMgt.get().hasCache(p_info.getTableName())) {
trxdel.addTrxEventListener(new TrxEventListener() {
@Override
public void afterRollback(Trx trxdel, boolean success) {
@ -4245,6 +4246,28 @@ public abstract class PO
}
});
}
// trigger the deletion of attachments and archives for after committed the delete
trxdel.addTrxEventListener(new TrxEventListener() {
@Override
public void afterRollback(Trx trxdel, boolean success) {
trxdel.removeTrxEventListener(this);
}
@Override
public void afterCommit(Trx trxdel, boolean success) {
if (success) {
if (m_KeyColumns != null && m_KeyColumns.length == 1 && !getTable().isUUIDKeyTable())
// Delete Cascade AD_Table_ID/Record_ID on Attachments/Archive
// after commit because operations on external storage providers don't have rollback
PO_Record.deleteRecordCascade(AD_Table_ID, Record_ID, "AD_Table.TableName IN ('AD_Attachment','AD_Archive')", null);
if (Record_UU != null)
PO_Record.deleteRecordCascade(AD_Table_ID, Record_UU, "AD_Table.TableName IN ('AD_Attachment','AD_Archive')", null);
}
trxdel.removeTrxEventListener(this);
}
@Override
public void afterClose(Trx trxdel) {
}
});
}
if (localTrx != null)
{

View File

@ -28,6 +28,7 @@ import org.compiere.util.DisplayType;
import org.compiere.util.Env;
import org.compiere.util.KeyNamePair;
import org.compiere.util.Msg;
import org.compiere.util.Util;
/**
* Maintain AD_Table_ID/Record_ID constraint
@ -47,10 +48,11 @@ public class PO_Record
* Delete Cascade including (selected)parent relationships
* @param AD_Table_ID table
* @param Record_IDorUU record ID (int) or UUID (String)
* @param whereTables filter for the Tables
* @param trxName transaction
* @return false if could not be deleted
*/
protected static boolean deleteRecordCascade (int AD_Table_ID, Serializable Record_IDorUU, String trxName)
protected static boolean deleteRecordCascade (int AD_Table_ID, Serializable Record_IDorUU, String whereTables, String trxName)
{
int refId;
String columnName;
@ -63,7 +65,7 @@ public class PO_Record
} else {
throw new IllegalArgumentException(Record_IDorUU.getClass().getName() + " not supported for ID/UUID");
}
KeyNamePair[] cascades = getTablesWithConstraintType(refId, MColumn.FKCONSTRAINTTYPE_ModelCascade, trxName);
KeyNamePair[] cascades = getTablesWithConstraintType(refId, MColumn.FKCONSTRAINTTYPE_ModelCascade, whereTables, trxName);
// Table Loop
StringBuilder whereClause = new StringBuilder("AD_Table_ID=? AND ").append(columnName).append("=?");
for (KeyNamePair table : cascades)
@ -146,21 +148,21 @@ public class PO_Record
KeyNamePair[] tables = s_po_record_tables_cache.get(key.toString());
if (tables != null)
return tables;
final String sql = ""
+ "SELECT t.AD_Table_ID, "
+ " c.ColumnName "
+ "FROM AD_Column c "
+ " JOIN AD_Table t ON c.AD_Table_ID = t.AD_Table_ID "
+ " LEFT JOIN AD_Ref_Table r ON c.AD_Reference_Value_ID = r.AD_Reference_ID "
+ " LEFT JOIN AD_Table tr ON r.AD_Table_ID = tr.AD_Table_ID "
+ "WHERE t.IsView = 'N' "
+ " AND t.IsActive = 'Y' "
+ " AND c.IsActive = 'Y' "
+ " AND ( ( c.AD_Reference_ID = ? "
+ " AND c.ColumnName = ? || '_ID' ) "
+ " OR ( c.AD_Reference_ID IN (? , ?) "
+ " AND ( tr.TableName = ? OR ( tr.TableName IS NULL AND c.ColumnName = ? || '_ID' ) ) ) ) "
+ " AND c.FKConstraintType = ?";
final String sql = """
SELECT t.AD_Table_ID,
c.ColumnName
FROM AD_Column c
JOIN AD_Table t ON c.AD_Table_ID = t.AD_Table_ID
LEFT JOIN AD_Ref_Table r ON c.AD_Reference_Value_ID = r.AD_Reference_ID
LEFT JOIN AD_Table tr ON r.AD_Table_ID = tr.AD_Table_ID
WHERE t.IsView = 'N'
AND t.IsActive = 'Y'
AND c.IsActive = 'Y'
AND ( ( c.AD_Reference_ID = ?
AND c.ColumnName = ? || '_ID' )
OR ( c.AD_Reference_ID IN (? , ?)
AND ( tr.TableName = ? OR ( tr.TableName IS NULL AND c.ColumnName = ? || '_ID' ) ) ) )
AND c.FKConstraintType = ?""";
List<List<Object>> dependents = DB.getSQLArrayObjectsEx(trxName, sql,
refTableDirId, tableName, refTableId, refTableSearchId, tableName, tableName, MColumn.FKCONSTRAINTTYPE_ModelCascade);
if (dependents != null) {
@ -275,6 +277,18 @@ public class PO_Record
* @return array of KeyNamePair<AD_Table_ID, TableName>
*/
private static KeyNamePair[] getTablesWithConstraintType(int refId, String constraintType, String trxName) {
return getTablesWithConstraintType(refId, constraintType, null, trxName);
}
/**
* Get array of tables which has a refId column with the defined Constraint Type
* @param refId AD_Reference_ID - Record_ID or Record_UU
* @param constraintType - FKConstraintType of AD_Column
* @param whereTables - optional filter
* @param trxName
* @return array of KeyNamePair<AD_Table_ID, TableName>
*/
private static KeyNamePair[] getTablesWithConstraintType(int refId, String constraintType, String whereTables, String trxName) {
String columnName;
if (refId == DisplayType.RecordID) {
columnName = "Record_ID";
@ -284,11 +298,14 @@ public class PO_Record
log.warning(refId + " not supported for ID/UUID");
return null;
}
StringBuilder key = new StringBuilder(constraintType).append("|").append(refId);
StringBuilder key = new StringBuilder(constraintType).append("|").append(refId).append("|").append(whereTables);
KeyNamePair[] tables = s_po_record_tables_cache.get(key.toString());
if (tables != null)
return tables;
List<MTable> listTables = new Query(Env.getCtx(), MTable.Table_Name, "c.AD_Reference_ID=? AND c.FKConstraintType=? AND AD_Table.IsView='N' AND c.ColumnName=?", trxName)
String whereClause = "c.AD_Reference_ID=? AND c.FKConstraintType=? AND AD_Table.IsView='N' AND c.ColumnName=?";
if (! Util.isEmpty(whereTables))
whereClause = whereClause + " AND (" + whereTables + ")";
List<MTable> listTables = new Query(Env.getCtx(), MTable.Table_Name, whereClause, trxName)
.addJoinClause("JOIN AD_Column c ON (c.AD_Table_ID=AD_Table.AD_Table_ID)")
.setOnlyActiveRecords(true)
.setParameters(refId, constraintType, columnName)