diff --git a/migration/i9/oracle/202112251630_IDEMPIERE-5126.sql b/migration/i9/oracle/202112251630_IDEMPIERE-5126.sql new file mode 100644 index 0000000000..f74a1927f4 --- /dev/null +++ b/migration/i9/oracle/202112251630_IDEMPIERE-5126.sql @@ -0,0 +1,18 @@ +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Virtual Column Test +-- Dec 25, 2021, 3:25:02 PM ART +INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,PrintName,EntityType,AD_Element_UU) VALUES (203552,0,0,'Y',TO_DATE('2021-12-25 15:25:01','YYYY-MM-DD HH24:MI:SS'),0,TO_DATE('2021-12-25 15:25:01','YYYY-MM-DD HH24:MI:SS'),0,'TestVirtualQty','Virtual Quantity','Used only for testing purposes','Test Virtual Column (Quantity)','D','c1b166c4-e265-47e1-b5f9-d9ce684df5aa') +; + +-- Dec 25, 2021, 3:26:11 PM ART +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,ColumnSQL,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml) VALUES (214655,0,'Virtual Quantity','Used only for testing purposes',135,'TestVirtualQty',22,'N','N','N','N','N',0,'N',29,0,0,'Y',TO_DATE('2021-12-25 15:26:10','YYYY-MM-DD HH24:MI:SS'),0,TO_DATE('2021-12-25 15:26:10','YYYY-MM-DD HH24:MI:SS'),0,203552,'N','N','D','N','N','(SELECT 123.45)','N','Y','db8b1287-434e-43a4-91fe-748141eb8ffb','N',0,'N','N','N') +; + +-- Dec 25, 2021, 5:14:12 PM ART +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,SortNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,AD_FieldGroup_ID,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,XPosition,ColumnSpan,NumLines,IsQuickEntry,IsDefaultFocus,IsAdvancedField,IsQuickForm) VALUES (206818,'Virtual Quantity','Used only for testing purposes',152,214655,'Y',0,260,0,'N','N','N','N',0,0,'Y',TO_DATE('2021-12-25 17:14:11','YYYY-MM-DD HH24:MI:SS'),0,TO_DATE('2021-12-25 17:14:11','YYYY-MM-DD HH24:MI:SS'),0,'N','Y',123,'D','240ab652-73b9-4dd1-b52f-0faa1d522817','Y',270,1,1,1,'N','N','N','N') +; + +SELECT register_migration_script('202112251630_IDEMPIERE-5126.sql') FROM dual +; diff --git a/migration/i9/postgresql/202112251630_IDEMPIERE-5126.sql b/migration/i9/postgresql/202112251630_IDEMPIERE-5126.sql new file mode 100644 index 0000000000..05f39dbc73 --- /dev/null +++ b/migration/i9/postgresql/202112251630_IDEMPIERE-5126.sql @@ -0,0 +1,16 @@ +-- Virtual Column Test +-- Dec 25, 2021, 3:25:02 PM ART +INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,PrintName,EntityType,AD_Element_UU) VALUES (203552,0,0,'Y',TO_TIMESTAMP('2021-12-25 15:25:01','YYYY-MM-DD HH24:MI:SS'),0,TO_TIMESTAMP('2021-12-25 15:25:01','YYYY-MM-DD HH24:MI:SS'),0,'TestVirtualQty','Virtual Quantity','Used only for testing purposes','Test Virtual Column (Quantity)','D','c1b166c4-e265-47e1-b5f9-d9ce684df5aa') +; + +-- Dec 25, 2021, 3:26:11 PM ART +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,ColumnSQL,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml) VALUES (214655,0,'Virtual Quantity','Used only for testing purposes',135,'TestVirtualQty',22,'N','N','N','N','N',0,'N',29,0,0,'Y',TO_TIMESTAMP('2021-12-25 15:26:10','YYYY-MM-DD HH24:MI:SS'),0,TO_TIMESTAMP('2021-12-25 15:26:10','YYYY-MM-DD HH24:MI:SS'),0,203552,'N','N','D','N','N','(SELECT 123.45)','N','Y','db8b1287-434e-43a4-91fe-748141eb8ffb','N',0,'N','N','N') +; + +-- Dec 25, 2021, 5:14:12 PM ART +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,SortNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,AD_FieldGroup_ID,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,XPosition,ColumnSpan,NumLines,IsQuickEntry,IsDefaultFocus,IsAdvancedField,IsQuickForm) VALUES (206818,'Virtual Quantity','Used only for testing purposes',152,214655,'Y',0,260,0,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2021-12-25 17:14:11','YYYY-MM-DD HH24:MI:SS'),0,TO_TIMESTAMP('2021-12-25 17:14:11','YYYY-MM-DD HH24:MI:SS'),0,'N','Y',123,'D','240ab652-73b9-4dd1-b52f-0faa1d522817','Y',270,1,1,1,'N','N','N','N') +; + + +SELECT register_migration_script('202112251630_IDEMPIERE-5126.sql') FROM dual +; diff --git a/org.adempiere.base/src/org/adempiere/base/AbstractModelFactory.java b/org.adempiere.base/src/org/adempiere/base/AbstractModelFactory.java index 6b76613196..2a97585557 100644 --- a/org.adempiere.base/src/org/adempiere/base/AbstractModelFactory.java +++ b/org.adempiere.base/src/org/adempiere/base/AbstractModelFactory.java @@ -54,20 +54,40 @@ public abstract class AbstractModelFactory implements IModelFactory { boolean errorLogged = false; try { + Exception ce = null; + Object[] arguments = null; Constructor constructor = null; try { constructor = clazz.getDeclaredConstructor(new Class[]{Properties.class, int.class, String.class}); + arguments = new Object[] {Env.getCtx(), Integer.valueOf(Record_ID), trxName}; } catch (Exception e) { - String msg = e.getMessage(); + ce = e; + } + if(constructor==null) + { + try + { + constructor = clazz.getDeclaredConstructor(new Class[]{Properties.class, int.class, String.class, String[].class}); + arguments = new Object[] {Env.getCtx(), Integer.valueOf(Record_ID), trxName, null}; + } + catch(Exception e) + { + ce = e; + } + } + + if(constructor==null && ce!=null) + { + String msg = ce.getMessage(); if (msg == null) - msg = e.toString(); + msg = ce.toString(); s_log.warning("No transaction Constructor for " + clazz + " (" + msg + ")"); } - PO po = constructor!=null ? (PO)constructor.newInstance(new Object[] {Env.getCtx(), Integer.valueOf(Record_ID), trxName}) : null; + PO po = constructor!=null ? (PO)constructor.newInstance(arguments) : null; return po; } catch (Exception e) diff --git a/org.adempiere.base/src/org/adempiere/model/GenericPO.java b/org.adempiere.base/src/org/adempiere/model/GenericPO.java index 8eb75d19db..bc368b3a8c 100644 --- a/org.adempiere.base/src/org/adempiere/model/GenericPO.java +++ b/org.adempiere.base/src/org/adempiere/model/GenericPO.java @@ -53,7 +53,7 @@ public class GenericPO extends PO implements DocAction { * @param ID */ public GenericPO(String tableName, Properties ctx, int ID) { - super(new PropertiesWrapper(ctx, tableName), ID, null, null); + super(new PropertiesWrapper(ctx, tableName), ID, null, (String[]) null); } /** @@ -72,7 +72,7 @@ public class GenericPO extends PO implements DocAction { * @param trxName */ public GenericPO(String tableName, Properties ctx, int ID, String trxName) { - super(new PropertiesWrapper(ctx, tableName), ID, trxName, null); + super(new PropertiesWrapper(ctx, tableName), ID, trxName, (String[]) null); } /** diff --git a/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java b/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java index 64849a5034..ef83d1b908 100644 --- a/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java +++ b/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java @@ -198,6 +198,19 @@ public class ModelClassGenerator .append(" }").append(NL) // Constructor End + // Standard Constructor + Virtual Columns + .append(NL) + .append(" /** Standard Constructor */").append(NL) + .append(" public ").append(className).append(" (Properties ctx, int ").append(keyColumn).append(", String trxName, String ... virtualColumns)").append(NL) + .append(" {").append(NL) + .append(" super (ctx, ").append(keyColumn).append(", trxName, virtualColumns);").append(NL) + .append(" /** if (").append(keyColumn).append(" == 0)").append(NL) + .append(" {").append(NL) + .append(mandatory) + .append(" } */").append(NL) + .append(" }").append(NL) + // Constructor End + // Load Constructor .append(NL) .append(" /** Load Constructor */").append(NL) @@ -267,7 +280,7 @@ public class ModelClassGenerator + "FROM AD_Column c " + "WHERE c.AD_Table_ID=?" + " AND c.ColumnName NOT IN ('AD_Client_ID', 'AD_Org_ID', 'IsActive', 'Created', 'CreatedBy', 'Updated', 'UpdatedBy')" - + " AND c.IsActive='Y'" + + " AND c.IsActive='Y' AND (c.ColumnSQL IS NULL OR c.ColumnSQL NOT LIKE '@SQL%') " + (!Util.isEmpty(entityTypeFilter) ? " AND c." + entityTypeFilter : "") + " ORDER BY c.ColumnName"; boolean isKeyNamePairCreated = false; // true if the method "getKeyNamePair" is already generated diff --git a/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java b/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java index 056ba3a2d0..77d744f5b8 100644 --- a/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java +++ b/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java @@ -248,7 +248,7 @@ public class ModelInterfaceGenerator + "FROM AD_Column c " + "WHERE c.AD_Table_ID=?" - + " AND c.IsActive='Y'" + + " AND c.IsActive='Y' AND (c.ColumnSQL IS NULL OR c.ColumnSQL NOT LIKE '@SQL%') " + (!Util.isEmpty(entityTypeFilter) ? " AND c." + entityTypeFilter : "") + " ORDER BY c.ColumnName"; PreparedStatement pstmt = null; diff --git a/org.adempiere.base/src/org/compiere/model/I_M_ProductionLine.java b/org.adempiere.base/src/org/compiere/model/I_M_ProductionLine.java index 08132a121a..be2ff39c97 100644 --- a/org.adempiere.base/src/org/compiere/model/I_M_ProductionLine.java +++ b/org.adempiere.base/src/org/compiere/model/I_M_ProductionLine.java @@ -22,7 +22,7 @@ import org.compiere.util.KeyNamePair; /** Generated Interface for M_ProductionLine * @author iDempiere (generated) - * @version Release 9 + * @version Release 10 */ public interface I_M_ProductionLine { @@ -266,32 +266,6 @@ public interface I_M_ProductionLine */ public boolean isProcessed(); - /** Column name ProductType */ - public static final String COLUMNNAME_ProductType = "ProductType"; - - /** Set Product Type. - * Type of product - */ - public void setProductType (String ProductType); - - /** Get Product Type. - * Type of product - */ - public String getProductType(); - - /** Column name QtyAvailable */ - public static final String COLUMNNAME_QtyAvailable = "QtyAvailable"; - - /** Set Available Quantity. - * Available Quantity (On Hand - Reserved) - */ - public void setQtyAvailable (BigDecimal QtyAvailable); - - /** Get Available Quantity. - * Available Quantity (On Hand - Reserved) - */ - public BigDecimal getQtyAvailable(); - /** Column name QtyUsed */ public static final String COLUMNNAME_QtyUsed = "QtyUsed"; diff --git a/org.adempiere.base/src/org/compiere/model/I_Test.java b/org.adempiere.base/src/org/compiere/model/I_Test.java index d39487c35d..b41e185842 100644 --- a/org.adempiere.base/src/org/compiere/model/I_Test.java +++ b/org.adempiere.base/src/org/compiere/model/I_Test.java @@ -348,6 +348,19 @@ public interface I_Test /** Get Test_UU */ public String getTest_UU(); + /** Column name TestVirtualQty */ + public static final String COLUMNNAME_TestVirtualQty = "TestVirtualQty"; + + /** Set TestVirtualQty. + * Used only for testing purposes + */ + public void setTestVirtualQty (BigDecimal TestVirtualQty); + + /** Get TestVirtualQty. + * Used only for testing purposes + */ + public BigDecimal getTestVirtualQty(); + /** Column name T_Integer */ public static final String COLUMNNAME_T_Integer = "T_Integer"; diff --git a/org.adempiere.base/src/org/compiere/model/MTest.java b/org.adempiere.base/src/org/compiere/model/MTest.java index af47160516..a83b349785 100644 --- a/org.adempiere.base/src/org/compiere/model/MTest.java +++ b/org.adempiere.base/src/org/compiere/model/MTest.java @@ -87,6 +87,10 @@ public class MTest extends X_Test } // MTest + public MTest(Properties ctx, int Test_ID, String trxName, String... virtualColumns) { + super(ctx, Test_ID, trxName, virtualColumns); + } + /** * Before Delete * @return true if it can be deleted diff --git a/org.adempiere.base/src/org/compiere/model/PO.java b/org.adempiere.base/src/org/compiere/model/PO.java index 91bb575681..54fb41c4b7 100644 --- a/org.adempiere.base/src/org/compiere/model/PO.java +++ b/org.adempiere.base/src/org/compiere/model/PO.java @@ -33,9 +33,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Properties; +import java.util.Set; import java.util.UUID; import java.util.logging.Level; import java.util.stream.Collectors; @@ -147,20 +149,32 @@ public abstract class PO */ public PO (Properties ctx) { - this (ctx, 0, null, null); + this (ctx, 0, null, null, (String[]) null); } // PO /** * Create and Load existing Persistent Object - * @param ID The unique ID of the object + * @param ID The unique ID of the object * @param ctx context * @param trxName transaction name */ public PO (Properties ctx, int ID, String trxName) { - this (ctx, ID, trxName, null); + this (ctx, ID, trxName, null, (String[]) null); } // PO + /** + * Create and load existing Persistent Object + * @param ctx Context + * @param ID Unique ID of the object + * @param trxName Transaction name + * @param virtualColumns names of virtual columns to load along with the regular table columns + */ + public PO (Properties ctx, int ID, String trxName, String ... virtualColumns) + { + this (ctx, ID, trxName, null, virtualColumns); + } + /** * Create and Load existing Persistent Object. * @param ctx context @@ -190,8 +204,9 @@ public abstract class PO * @param ID the ID if 0, the record defaults are applied - ignored if re exists * @param trxName transaction name * @param rs optional - load from current result set position (no navigation, not closed) + * @param virtualColumns optional - names of virtual columns to load along with the regular table columns */ - public PO (Properties ctx, int ID, String trxName, ResultSet rs) + public PO (Properties ctx, int ID, String trxName, ResultSet rs, String ... virtualColumns) { p_ctx = ctx != null ? ctx : Env.getCtx(); m_trxName = trxName; @@ -207,9 +222,9 @@ public abstract class PO m_setErrorsFilled = false; if (rs != null) - load(rs); // will not have virtual columns + load(rs); else - load(ID, trxName); + load(ID, trxName, virtualColumns); checkCrossTenant(false); } // PO @@ -223,7 +238,7 @@ public abstract class PO */ public PO (Properties ctx, PO source, int AD_Client_ID, int AD_Org_ID) { - this (ctx, 0, null, null); // create new + this (ctx, 0, null, (String[]) null); // create new // if (source != null) copyValues (source, this); @@ -296,6 +311,9 @@ public abstract class PO private String[] m_optimisticLockingColumns = new String[] {"Updated"}; private Boolean m_useOptimisticLocking = null; + /** Indices of virtual columns that were already resolved */ + private Set loadedVirtualColumns = new HashSet<>(); + /** Access Level S__ 100 4 System info */ public static final int ACCESSLEVEL_SYSTEM = 4; /** Access Level _C_ 010 2 Client info */ @@ -487,6 +505,8 @@ public abstract class PO return null; return m_newValues[index]; } + if(p_info.isVirtualColumn(index) && p_info.isVirtualDBColumn(index)) + loadVirtualColumn(index); return m_oldValues[index]; } // get_Value @@ -1325,8 +1345,9 @@ public abstract class PO * Load record with ID * @param ID ID * @param trxName transaction name + * @param virtualColumns names of virtual columns to load along with the regular table columns */ - protected void load (int ID, String trxName) + protected void load (int ID, String trxName, String ... virtualColumns) { checkImmutable(); @@ -1335,7 +1356,7 @@ public abstract class PO { setKeyInfo(); m_IDs = new Object[] {Integer.valueOf(ID)}; - load(trxName); + load(trxName, virtualColumns); } else // new { @@ -1349,10 +1370,11 @@ public abstract class PO /** * Load record with UUID * - * @param uuID UUID + * @param uuID universally unique identifier * @param trxName transaction name + * @param virtualColumns names of virtual columns to load along with the regular table columns */ - public void loadByUU(String uuID, String trxName) + public void loadByUU(String uuID, String trxName, String ... virtualColumns) { if (Util.isEmpty(uuID, true)) { @@ -1366,25 +1388,27 @@ public abstract class PO if (log.isLoggable(Level.FINEST)) log.finest("uuID=" + uuID); - load(uuID,trxName); + load(uuID,trxName, virtualColumns); } // loadByUU /** * (re)Load record with m_ID[*] * @param trxName transaction + * @param virtualColumns names of virtual columns to load along with the regular table columns * @return true if loaded */ - public boolean load (String trxName) { - return load(null, trxName); + public boolean load (String trxName, String ... virtualColumns) { + return load(null, trxName, virtualColumns); } /** * (re)Load record with uuID * @param uuID RecrodUU * @param trxName transaction + * @param virtualColumns names of virtual columns to load along with the regular table columns * @return true if loaded */ - protected boolean load (String uuID,String trxName) + protected boolean load (String uuID, String trxName, String ... virtualColumns) { m_trxName = trxName; boolean success = true; @@ -1392,14 +1416,33 @@ public abstract class PO int size = get_ColumnCount(); for (int i = 0; i < size; i++) { - if (i != 0) - sql.append(","); String columnSQL = p_info.getColumnSQL(i); - if (!p_info.isVirtualColumn(i)) + if (p_info.isVirtualColumn(i)) + { + boolean lazyLoad = true; + if(virtualColumns != null) + { + for(String virtualColumn : virtualColumns) + { + if(p_info.getColumnName(i).equalsIgnoreCase(virtualColumn)) + { + lazyLoad = false; + break; + } + } + } + + if(lazyLoad) + continue; + + } + else { columnSQL = DB.getDatabase().quoteColumnName(columnSQL); } - sql.append(columnSQL); // Normal and Virtual Column + if (i != 0) + sql.append(","); + sql.append(columnSQL); } sql.append(" FROM ").append(p_info.getTableName()) .append(" WHERE ") @@ -1480,61 +1523,12 @@ public abstract class PO boolean success = true; int index = 0; log.finest("(rs)"); + loadedVirtualColumns.clear(); // load column values for (index = 0; index < size; index++) { - String columnName = p_info.getColumnName(index); - Class clazz = p_info.getColumnClass(index); - int dt = p_info.getColumnDisplayType(index); - try - { - if (clazz == Integer.class) - m_oldValues[index] = decrypt(index, Integer.valueOf(rs.getInt(columnName))); - else if (clazz == BigDecimal.class) - m_oldValues[index] = decrypt(index, rs.getBigDecimal(columnName)); - else if (clazz == Boolean.class) - m_oldValues[index] = Boolean.valueOf("Y".equals(decrypt(index, rs.getString(columnName)))); - else if (clazz == Timestamp.class) - m_oldValues[index] = decrypt(index, rs.getTimestamp(columnName)); - else if (DisplayType.isLOB(dt)) - m_oldValues[index] = get_LOB (rs.getObject(columnName)); - else if (clazz == String.class) - { - String value = (String)decrypt(index, rs.getString(columnName)); - if (value != null) - { - if (get_Table_ID() == I_AD_Column.Table_ID || get_Table_ID() == I_AD_Element.Table_ID - || get_Table_ID() == I_AD_Field.Table_ID) - { - if ("Description".equals(columnName) || "Help".equals(columnName)) - { - value = value.intern(); - } - } - } - m_oldValues[index] = value; - } - else - m_oldValues[index] = loadSpecial(rs, index); - // NULL - if (rs.wasNull() && m_oldValues[index] != null) - m_oldValues[index] = null; - // - if (CLogMgt.isLevelAll()) - log.finest(String.valueOf(index) + ": " + p_info.getColumnName(index) - + "(" + p_info.getColumnClass(index) + ") = " + m_oldValues[index]); - } - catch (SQLException e) - { - if (p_info.isVirtualColumn(index)) { // if rs constructor used - if (log.isLoggable(Level.FINER))log.log(Level.FINER, "Virtual Column not loaded: " + columnName); - } else { - log.log(Level.SEVERE, "(rs) - " + String.valueOf(index) - + ": " + p_info.getTableName() + "." + p_info.getColumnName(index) - + " (" + p_info.getColumnClass(index) + ") - " + e); - success = false; - } - } + if(!loadColumn(rs, index) && success) + success = false; } m_createNew = false; setKeyInfo(); @@ -1542,6 +1536,103 @@ public abstract class PO return success; } // load + /** + * Load column value coming from a {@link ResultSet}. + * @param rs {@link ResultSet} with its position set according to the model class instance. + * @param index Column index. Might not coincide with the index of the column within the {@link ResultSet}. + * @return + * @see #m_oldValues + * @see POInfo#getColumnIndex(String) + */ + private boolean loadColumn(ResultSet rs, int index) { + boolean success = true; + String columnName = p_info.getColumnName(index); + Class clazz = p_info.getColumnClass(index); + int dt = p_info.getColumnDisplayType(index); + try + { + if (clazz == Integer.class) + m_oldValues[index] = decrypt(index, Integer.valueOf(rs.getInt(columnName))); + else if (clazz == BigDecimal.class) + m_oldValues[index] = decrypt(index, rs.getBigDecimal(columnName)); + else if (clazz == Boolean.class) + m_oldValues[index] = Boolean.valueOf("Y".equals(decrypt(index, rs.getString(columnName)))); + else if (clazz == Timestamp.class) + m_oldValues[index] = decrypt(index, rs.getTimestamp(columnName)); + else if (DisplayType.isLOB(dt)) + m_oldValues[index] = get_LOB (rs.getObject(columnName)); + else if (clazz == String.class) + { + String value = (String)decrypt(index, rs.getString(columnName)); + if (value != null) + { + if (get_Table_ID() == I_AD_Column.Table_ID || get_Table_ID() == I_AD_Element.Table_ID + || get_Table_ID() == I_AD_Field.Table_ID) + { + if ("Description".equals(columnName) || "Help".equals(columnName)) + { + value = value.intern(); + } + } + } + m_oldValues[index] = value; + } + else + m_oldValues[index] = loadSpecial(rs, index); + // NULL + if (rs.wasNull() && m_oldValues[index] != null) + m_oldValues[index] = null; + + // flag virtual column as loaded + if(p_info.isVirtualColumn(index)) + loadedVirtualColumns.add(index); + // + if (CLogMgt.isLevelAll()) + log.finest(String.valueOf(index) + ": " + p_info.getColumnName(index) + + "(" + p_info.getColumnClass(index) + ") = " + m_oldValues[index]); + } + catch (SQLException e) + { + if (p_info.isVirtualColumn(index)) { + if (log.isLoggable(Level.FINER))log.log(Level.FINER, "Virtual Column not loaded: " + columnName); + } else { + log.log(Level.SEVERE, "(rs) - " + String.valueOf(index) + + ": " + p_info.getTableName() + "." + p_info.getColumnName(index) + + " (" + p_info.getColumnClass(index) + ") - " + e); + success = false; + } + } + return success; + } + + /** + * Load value for virtual column, only if it wasn't loaded previously. + * @param index Column index (see {@link POInfo#getColumnIndex(String)}). + */ + private void loadVirtualColumn(int index) { + if(!m_createNew && !loadedVirtualColumns.contains(index)) { + StringBuilder sql = new StringBuilder("SELECT ").append(p_info.getColumnSQL(index)) + .append(" FROM ").append(p_info.getTableName()).append(" WHERE ") + .append(get_WhereClause(true, null)); + ResultSet rs = null; + PreparedStatement pstmt = null; + try + { + pstmt = DB.prepareStatement(sql.toString(), m_trxName); + rs = pstmt.executeQuery(); + if (rs.next()) + loadColumn(rs, index); + loadedVirtualColumns.add(index); + }catch(Exception e){ + log.log(Level.SEVERE, "(rs) - " + String.valueOf(index) + + ": " + p_info.getTableName() + "." + p_info.getColumnName(index) + + " (" + p_info.getColumnClass(index) + ") - " + e); + }finally { + DB.close(rs, pstmt); + } + } + } + /** * Load from HashMap * @param hmIn hash map @@ -1587,7 +1678,7 @@ public abstract class PO } catch (Exception e) { - if (p_info.isVirtualColumn(index)) { // if rs constructor used + if (p_info.isVirtualColumn(index)) { if (log.isLoggable(Level.FINER))log.log(Level.FINER, "Virtual Column not loaded: " + columnName); } else { log.log(Level.SEVERE, "(ht) - " + String.valueOf(index) diff --git a/org.adempiere.base/src/org/compiere/model/POInfo.java b/org.adempiere.base/src/org/compiere/model/POInfo.java index 8589d86a90..c38661269d 100644 --- a/org.adempiere.base/src/org/compiere/model/POInfo.java +++ b/org.adempiere.base/src/org/compiere/model/POInfo.java @@ -800,23 +800,35 @@ public class POInfo implements Serializable } return null; } // validate - + /** - * Build select clause - * @return stringbuilder + * Build SQL SELECT statement. + * @return {@link StringBuilder} instance with the SQL statement. */ public StringBuilder buildSelect() { return buildSelect(false, false); } - + /** - * Build select clause - * @param fullyQualified - * @param noVirtualColumn - * @return stringbuilder + * Build SQL SELECT statement. + * @param fullyQualified prefix column names with the table name + * @param noVirtualColumn Include (false value) all declared virtual columns at once + * or use lazy loading (true value). + * @return {@link StringBuilder} instance with the SQL statement. */ - public StringBuilder buildSelect(boolean fullyQualified, boolean noVirtualColumn) + public StringBuilder buildSelect(boolean fullyQualified, boolean noVirtualColumn) { + return buildSelect(fullyQualified, noVirtualColumn ? new String[] {} : null); + } + + /** + * Build SQL SELECT statement. + * @param fullyQualified prefix column names with the table name + * @param virtualColumns names of virtual columns to include along with the regular table columns - + * if no value (or null) is provided then all declared virtual columns will be included. + * @return {@link StringBuilder} instance with the SQL statement. + */ + public StringBuilder buildSelect(boolean fullyQualified, String ... virtualColumns) { StringBuilder sql = new StringBuilder("SELECT "); int size = getColumnCount(); @@ -824,9 +836,21 @@ public class POInfo implements Serializable for (int i = 0; i < size; i++) { boolean virtual = isVirtualColumn(i); - if (virtual && noVirtualColumn) - continue; - + if (virtual && virtualColumns != null) + { + boolean found = false; + for(String virtualColumn : virtualColumns) + { + if(m_columns[i].ColumnName.equalsIgnoreCase(virtualColumn)) + { + found = true; + break; + } + } + if(!found) + continue; + } + count++; if (count > 1) sql.append(","); diff --git a/org.adempiere.base/src/org/compiere/model/Query.java b/org.adempiere.base/src/org/compiere/model/Query.java index edca6b1726..b0df41ba8c 100644 --- a/org.adempiere.base/src/org/compiere/model/Query.java +++ b/org.adempiere.base/src/org/compiere/model/Query.java @@ -72,6 +72,12 @@ public class Query private String orderBy = null; private String trxName = null; private Object[] parameters = null; + + /** + * Name of virtual columns to be included in the query + */ + private String[] virtualColumns = null; + private boolean applyAccessFilter = false; private boolean applyAccessFilterRW = false; private boolean applyAccessFilterFullyQualified = true; @@ -79,7 +85,13 @@ public class Query private boolean onlyClient_ID = false; private int onlySelection_ID = -1; private boolean forUpdate = false; - private boolean noVirtualColumn = false; + + /** + * Whether to load (false value) all declared virtual columns at once or use + * lazy loading (true value). + */ + private boolean noVirtualColumn = true; + private int queryTimeout = 0; private List joinClauseList = new ArrayList(); @@ -247,7 +259,14 @@ public class Query this.forUpdate = forUpdate; return this; } - + + /** + * Virtual columns are lazy loaded by default. In case lazy loading is not desired use this method with + * the false value. + * @param noVirtualColumn Whether to load (false value) all declared virtual columns at once or use lazy loading (true value). + * @return + * @see #setVirtualColumns(String...) + */ public Query setNoVirtualColumn(boolean noVirtualColumn) { this.noVirtualColumn = noVirtualColumn; @@ -685,8 +704,8 @@ public class Query } /** - * Build SQL Clause - * @param selectClause optional; if null the select clause will be build according to POInfo + * Build SQL SELECT statement. + * @param selectClause optional; if null the select statement will be built by {@link POInfo} * @return final SQL */ private final String buildSQL(StringBuilder selectClause, boolean useOrderByClause) @@ -698,7 +717,11 @@ public class Query { throw new IllegalStateException("No POInfo found for AD_Table_ID="+table.getAD_Table_ID()); } - selectClause = info.buildSelect(!joinClauseList.isEmpty(), noVirtualColumn); + boolean isFullyQualified = !joinClauseList.isEmpty(); + if(virtualColumns == null) + selectClause = info.buildSelect(isFullyQualified, noVirtualColumn); + else + selectClause = info.buildSelect(isFullyQualified, virtualColumns); } if (!joinClauseList.isEmpty()) { @@ -926,4 +949,13 @@ public class Query return retValue; } // get_IDs + /** + * Virtual columns to be included in the query. + * @param virtualColumns virtual column names + */ + public Query setVirtualColumns(String ... virtualColumns) { + this.virtualColumns = virtualColumns; + return this; + } + } diff --git a/org.adempiere.base/src/org/compiere/model/X_M_ProductionLine.java b/org.adempiere.base/src/org/compiere/model/X_M_ProductionLine.java index d13fe2ab43..49a551c2d1 100644 --- a/org.adempiere.base/src/org/compiere/model/X_M_ProductionLine.java +++ b/org.adempiere.base/src/org/compiere/model/X_M_ProductionLine.java @@ -25,7 +25,7 @@ import org.compiere.util.KeyNamePair; /** Generated Model for M_ProductionLine * @author iDempiere (generated) - * @version Release 9 - $Id$ */ + * @version Release 10 - $Id$ */ @org.adempiere.base.Model(table="M_ProductionLine") public class X_M_ProductionLine extends PO implements I_M_ProductionLine, I_Persistent { @@ -33,7 +33,7 @@ public class X_M_ProductionLine extends PO implements I_M_ProductionLine, I_Pers /** * */ - private static final long serialVersionUID = 20211224L; + private static final long serialVersionUID = 20211226L; /** Standard Constructor */ public X_M_ProductionLine (Properties ctx, int M_ProductionLine_ID, String trxName) @@ -385,39 +385,6 @@ public class X_M_ProductionLine extends PO implements I_M_ProductionLine, I_Pers return false; } - /** Set Product Type. - @param ProductType Type of product - */ - public void setProductType (String ProductType) - { - throw new IllegalArgumentException ("ProductType is virtual column"); } - - /** Get Product Type. - @return Type of product - */ - public String getProductType() - { - return (String)get_Value(COLUMNNAME_ProductType); - } - - /** Set Available Quantity. - @param QtyAvailable Available Quantity (On Hand - Reserved) - */ - public void setQtyAvailable (BigDecimal QtyAvailable) - { - throw new IllegalArgumentException ("QtyAvailable is virtual column"); } - - /** Get Available Quantity. - @return Available Quantity (On Hand - Reserved) - */ - public BigDecimal getQtyAvailable() - { - BigDecimal bd = (BigDecimal)get_Value(COLUMNNAME_QtyAvailable); - if (bd == null) - return Env.ZERO; - return bd; - } - /** Set Quantity Used. @param QtyUsed Quantity Used */ diff --git a/org.adempiere.base/src/org/compiere/model/X_Test.java b/org.adempiere.base/src/org/compiere/model/X_Test.java index 41b3d44116..480bfe767e 100644 --- a/org.adempiere.base/src/org/compiere/model/X_Test.java +++ b/org.adempiere.base/src/org/compiere/model/X_Test.java @@ -26,7 +26,7 @@ import org.compiere.util.KeyNamePair; /** Generated Model for Test * @author iDempiere (generated) - * @version Release 9 - $Id$ */ + * @version Release 10 - $Id$ */ @org.adempiere.base.Model(table="Test") public class X_Test extends PO implements I_Test, I_Persistent { @@ -34,7 +34,7 @@ public class X_Test extends PO implements I_Test, I_Persistent /** * */ - private static final long serialVersionUID = 20211224L; + private static final long serialVersionUID = 20220102L; /** Standard Constructor */ public X_Test (Properties ctx, int Test_ID, String trxName) @@ -47,6 +47,17 @@ public class X_Test extends PO implements I_Test, I_Persistent } */ } + /** Standard Constructor */ + public X_Test (Properties ctx, int Test_ID, String trxName, String ... virtualColumns) + { + super (ctx, Test_ID, trxName, virtualColumns); + /** if (Test_ID == 0) + { + setName (null); + setTest_ID (0); + } */ + } + /** Load Constructor */ public X_Test (Properties ctx, ResultSet rs, String trxName) { @@ -530,6 +541,24 @@ public class X_Test extends PO implements I_Test, I_Persistent return (String)get_Value(COLUMNNAME_Test_UU); } + /** Set Virtual Quantity. + @param TestVirtualQty Used only for testing purposes + */ + public void setTestVirtualQty (BigDecimal TestVirtualQty) + { + throw new IllegalArgumentException ("TestVirtualQty is virtual column"); } + + /** Get Virtual Quantity. + @return Used only for testing purposes + */ + public BigDecimal getTestVirtualQty() + { + BigDecimal bd = (BigDecimal)get_Value(COLUMNNAME_TestVirtualQty); + if (bd == null) + return Env.ZERO; + return bd; + } + /** Set Integer. @param T_Integer Integer */ diff --git a/org.idempiere.test/src/org/idempiere/test/base/POTest.java b/org.idempiere.test/src/org/idempiere/test/base/POTest.java index a6b38ed785..90a29bd555 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/POTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/POTest.java @@ -32,6 +32,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.Properties; import org.adempiere.exceptions.DBException; @@ -449,4 +451,21 @@ public class POTest extends AbstractTestCase msg2 = new MMessage(Env.getCtx(), msg1.getAD_Message_ID(), getTrxName()); assertEquals(msg1.getMsgText(), msg2.getMsgText()); } + + @Test + public void testVirtualColumnLoad() { + MTest testPo = new MTest(Env.getCtx(), getClass().getName(), 1); + testPo.save(); + + // asynchronous (default) virtual column loading + assertTrue(null == testPo.get_ValueOld(MTest.COLUMNNAME_TestVirtualQty)); + BigDecimal expected = new BigDecimal(123.45d).setScale(2, RoundingMode.HALF_DOWN); + assertTrue(expected.compareTo(testPo.getTestVirtualQty()) == 0); + + // synchronous virtual column loading + testPo = new MTest(Env.getCtx(), testPo.get_ID(), getTrxName(), MTest.COLUMNNAME_TestVirtualQty); + assertTrue(null != testPo.get_ValueOld(MTest.COLUMNNAME_TestVirtualQty)); + assertTrue(expected.compareTo(testPo.getTestVirtualQty()) == 0); + } + } diff --git a/org.idempiere.test/src/org/idempiere/test/base/QueryTest.java b/org.idempiere.test/src/org/idempiere/test/base/QueryTest.java index 7a3fd36c69..b918c7ff6d 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/QueryTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/QueryTest.java @@ -30,15 +30,21 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.adempiere.exceptions.DBException; +import org.adempiere.model.POWrapper; +import org.compiere.model.I_Test; import org.compiere.model.MPInstance; import org.compiere.model.MProcess; import org.compiere.model.MTable; +import org.compiere.model.MTest; +import org.compiere.model.PO; import org.compiere.model.POResultSet; import org.compiere.model.Query; import org.compiere.model.X_AD_Element; @@ -333,4 +339,34 @@ public class QueryTest extends AbstractTestCase { assertEquals(expected, ids[i], "Element "+i+" not equals"); } } + + @Test + public void testVirtualColumnLoad() { + // create bogus record + PO testPo = new MTest(Env.getCtx(), getClass().getName(), 1); + testPo.save(); + + BigDecimal expected = new BigDecimal(123.45d).setScale(2, RoundingMode.HALF_DOWN); + + // virtual column lazy loading + Query query = new Query(Env.getCtx(), MTest.Table_Name, MTest.COLUMNNAME_Test_ID + "=?", getTrxName()); + testPo = query.setParameters(testPo.get_ID()).first(); + I_Test testRecord = POWrapper.create(testPo, I_Test.class); + assertTrue(null == testPo.get_ValueOld(MTest.COLUMNNAME_TestVirtualQty)); + assertTrue(expected.compareTo(testRecord.getTestVirtualQty()) == 0); + + // without virtual column lazy loading + testPo = query.setNoVirtualColumn(false).setParameters(testPo.get_ID()).first(); + assertTrue(null != testPo.get_ValueOld(MTest.COLUMNNAME_TestVirtualQty)); + testRecord = POWrapper.create(testPo, I_Test.class); + assertTrue(expected.compareTo(testRecord.getTestVirtualQty()) == 0); + + // single virtual column without lazy loading + testPo = query.setVirtualColumns(I_Test.COLUMNNAME_TestVirtualQty) + .setParameters(testPo.get_ID()).first(); + assertTrue(null != testPo.get_ValueOld(MTest.COLUMNNAME_TestVirtualQty)); + testRecord = POWrapper.create(testPo, I_Test.class); + assertTrue(expected.compareTo(testRecord.getTestVirtualQty()) == 0); + } + }