diff --git a/migration/iD12/oracle/202405080546_IDEMPIERE-6133.sql b/migration/iD12/oracle/202405080546_IDEMPIERE-6133.sql new file mode 100644 index 0000000000..d87569972b --- /dev/null +++ b/migration/iD12/oracle/202405080546_IDEMPIERE-6133.sql @@ -0,0 +1,13 @@ +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- May 8, 2024, 5:46:45 AM WIB +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200246,0,0,TO_TIMESTAMP('2024-05-08 05:46:45','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-05-08 05:46:45','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','EXPORT_BLOB_COLUMN_FOR_INSERT','Y','include blob column when export record using sql insert script option','D','C','c63dc8f1-c098-4f93-b84a-0897c0cf9c6a') +; + +-- May 8, 2024, 12:16:14 PM CEST +UPDATE AD_Column SET FieldLength=0, AD_Reference_ID=23,Updated=TO_DATE('2024-05-08 12:16:14','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=10011 +; + +-- IDEMPIERE-6133 +SELECT register_migration_script('202405080546_IDEMPIERE-6133.sql') FROM dual; diff --git a/migration/iD12/postgresql/202405080546_IDEMPIERE-6133.sql b/migration/iD12/postgresql/202405080546_IDEMPIERE-6133.sql new file mode 100644 index 0000000000..5955965de4 --- /dev/null +++ b/migration/iD12/postgresql/202405080546_IDEMPIERE-6133.sql @@ -0,0 +1,10 @@ +-- May 8, 2024, 5:46:45 AM WIB +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200246,0,0,TO_TIMESTAMP('2024-05-08 05:46:45','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-05-08 05:46:45','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','EXPORT_BLOB_COLUMN_FOR_INSERT','Y','include blob column when export record using sql insert script option','D','C','c63dc8f1-c098-4f93-b84a-0897c0cf9c6a') +; + +-- May 8, 2024, 12:16:14 PM CEST +UPDATE AD_Column SET FieldLength=0, AD_Reference_ID=23,Updated=TO_TIMESTAMP('2024-05-08 12:16:14','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=10011 +; + +-- IDEMPIERE-6133 +SELECT register_migration_script('202405080546_IDEMPIERE-6133.sql') FROM dual; diff --git a/org.adempiere.base/src/org/adempiere/impexp/GridTabSQLInsertExporter.java b/org.adempiere.base/src/org/adempiere/impexp/GridTabSQLInsertExporter.java index 402ac04bb0..5ed248d924 100644 --- a/org.adempiere.base/src/org/adempiere/impexp/GridTabSQLInsertExporter.java +++ b/org.adempiere.base/src/org/adempiere/impexp/GridTabSQLInsertExporter.java @@ -86,7 +86,7 @@ public void export(GridTab gridTab, List childs, boolean isCurrentRowOn ZipEntry fileEntry = new ZipEntry("oracle/" + table.getTableName() + ".sql"); zos.putNextEntry(fileEntry); for(String oracle : oracles) - zos.write((oracle+"\n;\n").getBytes()); + zos.write(!(oracle.endsWith("/")) ? (oracle+"\n;\n").getBytes() : (oracle+"\n").getBytes()); zos.closeEntry(); fileEntry = new ZipEntry("postgresql/" + table.getTableName() + ".sql"); @@ -137,9 +137,8 @@ public void export(GridTab gridTab, List childs, boolean isCurrentRowOn * @param pgs list to add postgresql insert script */ protected void addSQLInsert(PO po, List oracles, List pgs) { - String sql = po.toInsertSQL(); - String oracle = Database.getDatabase(Database.DB_ORACLE).convertStatement(sql); - String pg = Database.getDatabase(Database.DB_POSTGRESQL).convertStatement(sql); + String oracle = Database.getDatabase(Database.DB_ORACLE).convertStatement(po.toInsertSQL(Database.DB_ORACLE)); + String pg = Database.getDatabase(Database.DB_POSTGRESQL).convertStatement(po.toInsertSQL(Database.DB_POSTGRESQL)); oracles.add(oracle); pgs.add(pg); } diff --git a/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java b/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java index 966f233e29..5c9fdfa3b9 100644 --- a/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java +++ b/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java @@ -488,6 +488,13 @@ public default String getSQLCreate(MTable table) return sb.toString(); } // getSQLCreate + /** + * Convert blob to hex encoded string and return SQL function that will convert the hex encoded string back to blob + * @param blob + * @return SQL statement + */ + public String TO_Blob(byte[] blob); + /** * @param column * @return DDL SQL statement for column diff --git a/org.adempiere.base/src/org/compiere/model/MSysConfig.java b/org.adempiere.base/src/org/compiere/model/MSysConfig.java index 6784a5f8de..068717a723 100644 --- a/org.adempiere.base/src/org/compiere/model/MSysConfig.java +++ b/org.adempiere.base/src/org/compiere/model/MSysConfig.java @@ -111,6 +111,7 @@ public class MSysConfig extends X_AD_SysConfig public static final String ENABLE_SSO = "ENABLE_SSO"; public static final String ENABLE_SSO_OSGI_CONSOLE = "ENABLE_SSO_OSGI_CONSOLE"; public static final String ENABLE_SSO_IDEMPIERE_MONITOR = "ENABLE_SSO_IDEMPIERE_MONITOR"; + public static final String EXPORT_BLOB_COLUMN_FOR_INSERT = "EXPORT_BLOB_COLUMN_FOR_INSERT"; public static final String FEEDBACK_EMAIL_CC = "FEEDBACK_EMAIL_CC"; public static final String FEEDBACK_EMAIL_TO = "FEEDBACK_EMAIL_TO"; public static final String FORCE_POSTING_PRIOR_TO_PERIOD_CLOSE = "FORCE_POSTING_PRIOR_TO_PERIOD_CLOSE"; diff --git a/org.adempiere.base/src/org/compiere/model/PO.java b/org.adempiere.base/src/org/compiere/model/PO.java index 33399a8826..de4c6ca650 100644 --- a/org.adempiere.base/src/org/compiere/model/PO.java +++ b/org.adempiere.base/src/org/compiere/model/PO.java @@ -36,6 +36,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.Set; @@ -59,6 +60,7 @@ import org.compiere.Adempiere; import org.compiere.acct.Doc; import org.compiere.db.AdempiereDatabase; +import org.compiere.db.Database; import org.compiere.util.AdempiereUserError; import org.compiere.util.CCache; import org.compiere.util.CLogMgt; @@ -3464,7 +3466,7 @@ private boolean doInsert(boolean withValues) { // SQL StringBuilder sqlInsert = new StringBuilder(); - AD_ChangeLog_ID = buildInsertSQL(sqlInsert, withValues, params, session, AD_ChangeLog_ID, false); + AD_ChangeLog_ID = buildInsertSQL(sqlInsert, withValues, params, session, AD_ChangeLog_ID, false, null); // int no = withValues ? DB.executeUpdate(sqlInsert.toString(), m_trxName) : DB.executeUpdate(sqlInsert.toString(), params.toArray(), false, m_trxName); @@ -3533,12 +3535,13 @@ private boolean doInsert(boolean withValues) { /** * Export data as insert SQL statement + * @param database * @return SQL insert statement */ - public String toInsertSQL() + public String toInsertSQL(String database) { StringBuilder sqlInsert = new StringBuilder(); - buildInsertSQL(sqlInsert, true, null, null, 0, true); + buildInsertSQL(sqlInsert, true, null, null, 0, true, database); return sqlInsert.toString(); } @@ -3553,12 +3556,13 @@ public String toInsertSQL() * @return last AD_ChangeLog_ID */ protected int buildInsertSQL(StringBuilder sqlInsert, boolean withValues, List params, MSession session, - int AD_ChangeLog_ID, boolean generateScriptOnly) { + int AD_ChangeLog_ID, boolean generateScriptOnly, String database) { sqlInsert.append("INSERT INTO "); sqlInsert.append(p_info.getTableName()).append(" ("); StringBuilder sqlValues = new StringBuilder(") VALUES ("); int size = get_ColumnCount(); boolean doComma = false; + Map oracleBlobSQL = new HashMap(); for (int i = 0; i < size; i++) { Object value = get_Value(i); @@ -3572,8 +3576,6 @@ protected int buildInsertSQL(StringBuilder sqlInsert, boolean withValues, List 2048) + { + oracleBlobSQL.put(p_info.getColumnName(i), blobSQL); + blobSQL = p_info.isColumnMandatory(i) ? "'0'" : null; + } + sqlValues.append (blobSQL); } + else if (p_info.isColumnMandatory(i)) + { + sqlValues.append("'0'"); // no db dependent stuff here -- at this point value is known to be not null + } + else + { + sqlValues.append("null"); + } } else sqlValues.append (saveNewSpecial (value, i)); @@ -3825,6 +3838,47 @@ else if (c == String.class) } sqlInsert.append(sqlValues) .append(")"); + + // Use pl/sql block for Oracle blob insert that's > 2048 bytes + if (!oracleBlobSQL.isEmpty()) + { + sqlInsert.append("\n;"); + for(String column : oracleBlobSQL.keySet()) + { + sqlInsert.append("\n\n"); + String blobSQL = oracleBlobSQL.get(column); + int hexDataStart = blobSQL.indexOf("'"); + int hexDataEnd = blobSQL.indexOf("'", hexDataStart+1); + String functionStart = blobSQL.substring(0, hexDataStart); + String hexData = blobSQL.substring(hexDataStart+1, hexDataEnd); + String functionEnd = blobSQL.substring(hexDataEnd+1); + int remaining = hexData.length(); + int lineSize = 2048; + sqlInsert.append("DECLARE\n") + .append(" lob_out blob;\n") + .append("BEGIN\n") + .append(" UPDATE ").append(p_info.getTableName()) + .append(" SET ").append(column).append("=EMPTY_BLOB()\n") + .append(" WHERE ").append(getUUIDColumnName()).append("=") + .append("'").append(get_UUID()).append("';\n") + .append(" SELECT ").append(column).append(" INTO lob_out\n") + .append(" FROM ").append(p_info.getTableName()).append("\n") + .append(" WHERE ").append(getUUIDColumnName()).append("=") + .append("'").append(get_UUID()).append("'\n") + .append(" FOR UPDATE;\n"); + // Split hex encoded text into 2048 bytes block + int index = 0; + while (remaining > 0) + { + sqlInsert.append(" dbms_lob.append(lob_out, ").append(functionStart).append("'"); + String data = remaining > lineSize ? hexData.substring(index, index+lineSize) : hexData.substring(index); + sqlInsert.append(data).append("'").append(functionEnd).append(");\n"); + remaining = remaining > lineSize ? remaining - lineSize : 0; + index = index + lineSize; + } + sqlInsert.append("END;\n/"); + } + } return AD_ChangeLog_ID; } @@ -5419,7 +5473,7 @@ private boolean lobSave () for (int i = 0; i < m_lobInfo.size(); i++) { PO_LOB lob = (PO_LOB)m_lobInfo.get(i); - if (!lob.save(get_TrxName())) + if (!lob.save(get_WhereClause(true), get_TrxName())) { retValue = false; break; diff --git a/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java b/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java index e97381d417..1f339d2b41 100644 --- a/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java +++ b/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java @@ -31,6 +31,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; +import java.util.HexFormat; import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; @@ -1216,4 +1217,9 @@ public boolean isQueryTimeout(SQLException ex) { public ITablePartitionService getTablePartitionService() { return new TablePartitionService(); } + + @Override + public String TO_Blob(byte[] blob) { + return "HEXTORAW('"+HexFormat.of().formatHex(blob)+"')"; + } } // DB_Oracle diff --git a/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java b/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java index ff9c055054..6b1333e265 100755 --- a/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java +++ b/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java @@ -34,6 +34,7 @@ import java.sql.Statement; import java.sql.Timestamp; import java.util.Arrays; +import java.util.HexFormat; import java.util.List; import java.util.Properties; import java.util.concurrent.CountDownLatch; @@ -1405,4 +1406,9 @@ public boolean isQueryTimeout(SQLException ex) { public ITablePartitionService getTablePartitionService() { return new TablePartitionService(); } + + @Override + public String TO_Blob(byte[] blob) { + return "decode('"+HexFormat.of().formatHex(blob)+"','hex')"; + } } // DB_PostgreSQL