IDEMPIERE-5126 Virtual column lazy loading (#1095)
* IDEMPIERE-5126 Virtual column lazy loading Virtual columns are now loaded only whenever their respective getters are invoked. This functionality works irrespective of the PO constructor used. Query class now uses lazy loading by default as well. * IDEMPIERE-5126 Virtual column lazy loading Have the model generator ignore virtual columns with the @SQL prefix, as their generated getters would always return null. * IDEMPIERE-5126 Virtual column lazy loading Added quoting to virtual column names * IDEMPIERE-5126 Virtual column lazy loading Reload virtual columns if the record was reloaded. * IDEMPIERE-5126 Virtual column lazy loading Fixed class cast exception in unit test. * IDEMPIERE-5126 Virtual column lazy loading Support for loading selected virtual columns along with regular table columns, skipping lazy loading. * IDEMPIERE-5126 Virtual column lazy loading Fine-grained virtual column loading support for Query. * IDEMPIERE-5126 Virtual column lazy loading Support for loading selected virtual columns along with regular table columns, skipping lazy loading. * IDEMPIERE-5126 Virtual column lazy loading Improved support for the constructor PO.PO(Properties, int, String, String...). * IDEMPIERE-5126 Virtual column lazy loading Refactoring. Reduced size of messages sent and received from database engine when lazy loading virtual columns. Other minor changes. * IDEMPIERE-5126 Virtual column lazy loading Added testing assertions. * IDEMPIERE-5126 Virtual column lazy loading Silenced Eclipse warnings.
This commit is contained in:
parent
2fe6f5bba5
commit
0eb89932fd
|
@ -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
|
||||
;
|
|
@ -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
|
||||
;
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Integer> 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)
|
||||
|
|
|
@ -802,8 +802,8 @@ public class POInfo implements Serializable
|
|||
} // validate
|
||||
|
||||
/**
|
||||
* Build select clause
|
||||
* @return stringbuilder
|
||||
* Build SQL SELECT statement.
|
||||
* @return {@link StringBuilder} instance with the SQL statement.
|
||||
*/
|
||||
public StringBuilder buildSelect()
|
||||
{
|
||||
|
@ -811,12 +811,24 @@ public class POInfo implements Serializable
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 (<code>false</code> value) all declared virtual columns at once
|
||||
* or use lazy loading (<code>true</code> 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 <code>null</code>) 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,8 +836,20 @@ 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)
|
||||
|
|
|
@ -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 (<code>false</code> value) all declared virtual columns at once or use
|
||||
* lazy loading (<code>true</code> value).
|
||||
*/
|
||||
private boolean noVirtualColumn = true;
|
||||
|
||||
private int queryTimeout = 0;
|
||||
private List<String> joinClauseList = new ArrayList<String>();
|
||||
|
||||
|
@ -248,6 +260,13 @@ public class Query
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual columns are lazy loaded by default. In case lazy loading is not desired use this method with
|
||||
* the <code>false</code> value.
|
||||
* @param noVirtualColumn Whether to load (<code>false</code> value) all declared virtual columns at once or use lazy loading (<code>true</code> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue