Skip to content

Commit

Permalink
IDEMPIERE-6122 Query class to accept a list of columns to select (#2331)
Browse files Browse the repository at this point in the history
* IDEMPIERE-6122 Query class to accept a list of columns to select

* IDEMPIERE-6122 Query class to accept a list of columns to select

- improve efficiency
  • Loading branch information
hengsin committed Apr 26, 2024
1 parent 25e0fd0 commit a95fdc6
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 14 deletions.
30 changes: 30 additions & 0 deletions org.adempiere.base/src/org/compiere/model/MTable.java
Expand Up @@ -623,6 +623,36 @@ public PO getPO (int Record_ID, String trxName)
return po;
} // getPO

private static final ThreadLocal<String[]> partialPOResultSetColumns = new ThreadLocal<>();

/**
* Get columns included in result set of {@link #getPO(int, String)} call.<br/>
* Use by {@link #getPartialPO(ResultSet, String[], String)}.
* @return columns included in result set of {@link #getPO(int, String)} call
*/
protected static final String[] getPartialPOResultSetColumns() {
return partialPOResultSetColumns.get();
}

/**
* Get PO Instance from result set that only include some of the columns of the PO model.
* @param rs result set
* @param selectColumns
* @param trxName transaction
* @return immutable PO instance
*/
public final PO getPartialPO (ResultSet rs, String[] selectColumns, String trxName)
{
try {
partialPOResultSetColumns.set(selectColumns);
PO po = getPO(rs, trxName);
po.makeImmutable();
return po;
} finally {
partialPOResultSetColumns.remove();
}
}

/**
* Get PO Instance from result set
* @param rs result set
Expand Down
11 changes: 10 additions & 1 deletion org.adempiere.base/src/org/compiere/model/PO.java
Expand Up @@ -36,6 +36,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
Expand Down Expand Up @@ -1627,7 +1628,7 @@ protected boolean load (ResultSet rs)
int size = get_ColumnCount();
boolean success = true;
int index = 0;
log.finest("(rs)");
if (log.isLoggable(Level.FINEST)) log.finest("(rs)");
loadedVirtualColumns.clear();
// load column values
for (index = 0; index < size; index++)
Expand All @@ -1652,6 +1653,14 @@ protected boolean load (ResultSet rs)
private boolean loadColumn(ResultSet rs, int index) {
boolean success = true;
String columnName = p_info.getColumnName(index);
String[] selectColumns = MTable.getPartialPOResultSetColumns();
if (selectColumns != null && selectColumns.length > 0) {
Optional<String> optional = Arrays.stream(selectColumns).filter(e -> e.equalsIgnoreCase(columnName)).findFirst();
if (!optional.isPresent()) {
if (log.isLoggable(Level.FINER))log.log(Level.FINER, "Partial PO, Column not loaded: " + columnName);
return true;
}
}
Class<?> clazz = p_info.getColumnClass(index);
int dt = p_info.getColumnDisplayType(index);
try
Expand Down
45 changes: 45 additions & 0 deletions org.adempiere.base/src/org/compiere/model/POInfo.java
Expand Up @@ -25,8 +25,10 @@
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.logging.Level;

Expand Down Expand Up @@ -880,6 +882,49 @@ public StringBuilder buildSelect(boolean fullyQualified, String ... virtualColum
return sql;
}

/**
* Build SQL SELECT statement for columns.
* @param fullyQualified prefix column names with the table name
* @return {@link StringBuilder} instance with the SQL statement.
*/
public StringBuilder buildSelectForColumns(boolean fullyQualified, String[] columns)
{
StringBuilder sql = new StringBuilder("SELECT ");
int size = getColumnCount();
int count = 0;
String uuid = PO.getUUIDColumnName(m_TableName);
for (int i = 0; i < size; i++)
{
String columnName = getColumnName(i);
boolean virtual = isVirtualColumn(i);
boolean isKey = isKey(i);
boolean isUUID = columnName.equals(uuid);
//always include key, uuid and standard columns
if (!isKey && !isUUID && !columnName.equalsIgnoreCase("ad_client_id") && !columnName.equalsIgnoreCase("ad_org_id")
&& !columnName.equalsIgnoreCase("isactive") && !columnName.equalsIgnoreCase("created") && !columnName.equalsIgnoreCase("createdby")
&& !columnName.equalsIgnoreCase("updated") && !columnName.equalsIgnoreCase("updatedby"))
{
Optional<String> optional = Arrays.stream(columns).filter(e -> e.equalsIgnoreCase(columnName)).findFirst();
if (!optional.isPresent())
continue;
}

count++;
if (count > 1)
sql.append(",");
String columnSQL = getColumnSQL(i);
if (!virtual)
columnSQL = DB.getDatabase().quoteColumnName(columnSQL);
if (fullyQualified && !virtual)
sql.append(getTableName()).append(".");
sql.append(columnSQL); // Normal and Virtual Column
if (fullyQualified && !virtual)
sql.append(" AS ").append(m_columns[i].ColumnName);
}
sql.append(" FROM ").append(getTableName());
return sql;
}

/**
* Is save changes to change log table
* @return if table save change log
Expand Down
11 changes: 10 additions & 1 deletion org.adempiere.base/src/org/compiere/model/POResultSet.java
Expand Up @@ -44,6 +44,7 @@ public class POResultSet<T extends PO> implements AutoCloseable {
private T currentPO = null;
/** Should we close the statement and resultSet on any exception that occur ? */
private boolean closeOnError = true;
private String[] selectColumns;

/**
* Constructs the POResultSet.<br/>
Expand Down Expand Up @@ -87,7 +88,7 @@ public T next() throws DBException {
}
try {
if ( resultSet.next() ) {
return (T) table.getPO(resultSet, trxName);
return (T) (selectColumns != null && selectColumns.length > 0 ? table.getPartialPO(resultSet, selectColumns, trxName) : table.getPO(resultSet, trxName));
} else {
this.close(); // close it if there is no more data to read
return null;
Expand Down Expand Up @@ -135,4 +136,12 @@ public void close() {
this.statement = null;
currentPO = null;
}

/**
* Set columns for result set. Use for loading of partial PO.
* @param selectColumns
*/
public void setSelectColumns(String[] selectColumns) {
this.selectColumns = selectColumns;
}
}
59 changes: 47 additions & 12 deletions org.adempiere.base/src/org/compiere/model/Query.java
Expand Up @@ -110,6 +110,9 @@ public class Query
* Number of records will be skipped on query run.
*/
private int recordsToSkip;

/** list of columns to include in select statement (optional) */
private String[] selectColumns;

/**
* @param table
Expand Down Expand Up @@ -313,7 +316,6 @@ public void addTableDirectJoin(String foreignTableName) {
* @return PO List
* @throws DBException
*/
@SuppressWarnings("unchecked")
public <T extends PO> List<T> list() throws DBException
{
List<T> list = new ArrayList<T>();
Expand All @@ -327,7 +329,7 @@ public <T extends PO> List<T> list() throws DBException
rs = createResultSet(pstmt);
while (rs.next ())
{
T po = (T)table.getPO(rs, trxName);
T po = getPO(rs);
list.add(po);
}
}
Expand All @@ -346,8 +348,7 @@ public <T extends PO> List<T> list() throws DBException
* Get first PO that match query criteria
* @return first PO
* @throws DBException
*/
@SuppressWarnings("unchecked")
*/
public <T extends PO> T first() throws DBException
{
T po = null;
Expand All @@ -368,7 +369,7 @@ public <T extends PO> T first() throws DBException
rs = createResultSet(pstmt);
if (rs.next ())
{
po = (T)table.getPO(rs, trxName);
po = getPO(rs);
}
}
catch (SQLException e)
Expand All @@ -382,6 +383,22 @@ public <T extends PO> T first() throws DBException
}
return po;
}

/**
* Get partial or full PO
* @param <T>
* @param rs
* @return partial or full PO.
*/
@SuppressWarnings("unchecked")
private <T extends PO> T getPO(ResultSet rs) {
T po;
if (selectColumns != null && selectColumns.length > 0)
po = (T)table.getPartialPO(rs, selectColumns, trxName);
else
po = (T)table.getPO(rs, trxName);
return po;
}

/**
* Get first PO that match query criteria.<br/>
Expand All @@ -390,7 +407,6 @@ public <T extends PO> T first() throws DBException
* @throws DBException
* @see {@link #first()}
*/
@SuppressWarnings("unchecked")
public <T extends PO> T firstOnly() throws DBException
{
T po = null;
Expand All @@ -411,7 +427,7 @@ public <T extends PO> T firstOnly() throws DBException
rs = createResultSet(pstmt);
if (rs.next())
{
po = (T)table.getPO(rs, trxName);
po = getPO(rs);
}
if (rs.next())
{
Expand Down Expand Up @@ -712,8 +728,7 @@ public <T extends PO> Stream<T> stream() throws DBException
public boolean tryAdvance(Consumer<? super T> action) {
try {
if(!finalRS.next()) return false;
@SuppressWarnings("unchecked")
final T newRec = (T)table.getPO(finalRS, trxName);
final T newRec = getPO(finalRS);
action.accept(newRec);
return true;
} catch(SQLException ex) {
Expand Down Expand Up @@ -801,6 +816,10 @@ public <T extends PO> POResultSet<T> scroll() throws DBException
rs = createResultSet(pstmt);
rsPO = new POResultSet<T>(table, pstmt, rs, trxName);
rsPO.setCloseOnError(true);
if (selectColumns != null && selectColumns.length > 0)
{
rsPO.setSelectColumns(selectColumns);
}
return rsPO;
}
catch (SQLException e)
Expand Down Expand Up @@ -834,10 +853,17 @@ private final String buildSQL(StringBuilder selectClause, boolean useOrderByClau
throw new IllegalStateException("No POInfo found for AD_Table_ID="+table.getAD_Table_ID());
}
boolean isFullyQualified = !joinClauseList.isEmpty();
if(virtualColumns == null)
selectClause = info.buildSelect(isFullyQualified, noVirtualColumn);
if (selectColumns != null && selectColumns.length > 0)
{
selectClause = info.buildSelectForColumns(isFullyQualified, selectColumns);
}
else
selectClause = info.buildSelect(isFullyQualified, virtualColumns);
{
if(virtualColumns == null)
selectClause = info.buildSelect(isFullyQualified, noVirtualColumn);
else
selectClause = info.buildSelect(isFullyQualified, virtualColumns);
}
}
if (!joinClauseList.isEmpty())
{
Expand Down Expand Up @@ -1063,4 +1089,13 @@ public Query setVirtualColumns(String ... virtualColumns) {
return this;
}

/**
* Set the columns to include in select query.<br/>
* Note that this doesn't effect {@link #iterate()}.
* @param columns
*/
public Query selectColumns(String ...columns) {
this.selectColumns = columns;
return this;
}
}
58 changes: 58 additions & 0 deletions org.idempiere.test/src/org/idempiere/test/base/QueryTest.java
Expand Up @@ -25,7 +25,9 @@
package org.idempiere.test.base;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
Expand All @@ -46,6 +48,7 @@
import org.compiere.model.I_Test;
import org.compiere.model.MPInstance;
import org.compiere.model.MProcess;
import org.compiere.model.MProduct;
import org.compiere.model.MTable;
import org.compiere.model.MTest;
import org.compiere.model.MUser;
Expand Down Expand Up @@ -434,4 +437,59 @@ public void testTableDirectJoin() {
String sql = query.getSQL();
assertTrue(sql.toLowerCase().contains("inner join c_bpartner on (ad_user.c_bpartner_id=c_bpartner.c_bpartner_id)"), "Unexpected SQL clause generated from query");
}

@Test
public void testPartialPO() {
Query query = new Query(Env.getCtx(), MProduct.Table_Name, MProduct.COLUMNNAME_M_Product_ID + "=?", getTrxName());
MProduct product = query.setParameters(DictionaryIDs.M_Product.AZALEA_BUSH.id).first();
assertNotNull(product.getName());
assertNotNull(product.getValue());
assertNotNull(product.getProductType());
assertTrue(product.getM_Product_Category_ID() > 0);
assertFalse(product.is_Immutable());

product = query.selectColumns(MProduct.COLUMNNAME_Name, MProduct.COLUMNNAME_Value).setParameters(DictionaryIDs.M_Product.AZALEA_BUSH.id).first();
assertNotNull(product.getName());
assertNotNull(product.getValue());
assertNull(product.getProductType());
assertTrue(product.getM_Product_Category_ID() == 0);
assertTrue(product.is_Immutable());

product = query.selectColumns().setParameters(DictionaryIDs.M_Product.AZALEA_BUSH.id).first();
assertNotNull(product.getName());
assertNotNull(product.getValue());
assertNotNull(product.getProductType());
assertTrue(product.getM_Product_Category_ID() > 0);
assertFalse(product.is_Immutable());

List<MProduct> list = query.selectColumns(MProduct.COLUMNNAME_Name, MProduct.COLUMNNAME_Value).setParameters(DictionaryIDs.M_Product.AZALEA_BUSH.id).list();
product = list.get(0);
assertNotNull(product.getName());
assertNotNull(product.getValue());
assertNull(product.getProductType());
assertTrue(product.getM_Product_Category_ID() == 0);
assertTrue(product.is_Immutable());

product = query.selectColumns(MProduct.COLUMNNAME_Name, MProduct.COLUMNNAME_Value).setParameters(DictionaryIDs.M_Product.AZALEA_BUSH.id).firstOnly();
assertNotNull(product.getName());
assertNotNull(product.getValue());
assertNull(product.getProductType());
assertTrue(product.getM_Product_Category_ID() == 0);
assertTrue(product.is_Immutable());

product = (MProduct) query.selectColumns(MProduct.COLUMNNAME_Name, MProduct.COLUMNNAME_Value).setParameters(DictionaryIDs.M_Product.AZALEA_BUSH.id).scroll().next();
assertNotNull(product.getName());
assertNotNull(product.getValue());
assertNull(product.getProductType());
assertTrue(product.getM_Product_Category_ID() == 0);
assertTrue(product.is_Immutable());

Stream<MProduct> stream = query.selectColumns(MProduct.COLUMNNAME_Name, MProduct.COLUMNNAME_Value).setParameters(DictionaryIDs.M_Product.AZALEA_BUSH.id).stream();
product = stream.findFirst().get();
assertNotNull(product.getName());
assertNotNull(product.getValue());
assertNull(product.getProductType());
assertTrue(product.getM_Product_Category_ID() == 0);
assertTrue(product.is_Immutable());
}
}

0 comments on commit a95fdc6

Please sign in to comment.