mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Merge pull request #155 from Kingsrook/feature/join-record-enhancements
Feature/join record enhancements
This commit is contained in:
@ -71,6 +71,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAndJoinTable;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
||||||
@ -567,7 +568,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
// all pivotFields that are possible value sources are implicitly translated //
|
// all pivotFields that are possible value sources are implicitly translated //
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
QTableMetaData mainTable = QContext.getQInstance().getTable(dataSource.getSourceTable());
|
QTableMetaData mainTable = QContext.getQInstance().getTable(dataSource.getSourceTable());
|
||||||
FieldAndJoinTable fieldAndJoinTable = getFieldAndJoinTable(mainTable, summaryFieldName);
|
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(mainTable, summaryFieldName);
|
||||||
if(fieldAndJoinTable.field().getPossibleValueSourceName() != null)
|
if(fieldAndJoinTable.field().getPossibleValueSourceName() != null)
|
||||||
{
|
{
|
||||||
fieldsToTranslatePossibleValues.add(summaryFieldName);
|
fieldsToTranslatePossibleValues.add(summaryFieldName);
|
||||||
@ -580,32 +581,6 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public static FieldAndJoinTable getFieldAndJoinTable(QTableMetaData mainTable, String fieldName) throws QException
|
|
||||||
{
|
|
||||||
if(fieldName.indexOf('.') > -1)
|
|
||||||
{
|
|
||||||
String joinTableName = fieldName.replaceAll("\\..*", "");
|
|
||||||
String joinFieldName = fieldName.replaceAll(".*\\.", "");
|
|
||||||
|
|
||||||
QTableMetaData joinTable = QContext.getQInstance().getTable(joinTableName);
|
|
||||||
if(joinTable == null)
|
|
||||||
{
|
|
||||||
throw (new QException("Unrecognized join table name: " + joinTableName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new FieldAndJoinTable(joinTable.getField(joinFieldName), joinTable);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new FieldAndJoinTable(mainTable.getField(fieldName), mainTable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -756,7 +731,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
SummaryKey key = new SummaryKey();
|
SummaryKey key = new SummaryKey();
|
||||||
for(String summaryFieldName : view.getSummaryFields())
|
for(String summaryFieldName : view.getSummaryFields())
|
||||||
{
|
{
|
||||||
FieldAndJoinTable fieldAndJoinTable = getFieldAndJoinTable(table, summaryFieldName);
|
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, summaryFieldName);
|
||||||
Serializable summaryValue = record.getValue(summaryFieldName);
|
Serializable summaryValue = record.getValue(summaryFieldName);
|
||||||
if(fieldAndJoinTable.field().getPossibleValueSourceName() != null)
|
if(fieldAndJoinTable.field().getPossibleValueSourceName() != null)
|
||||||
{
|
{
|
||||||
@ -811,7 +786,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
//////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////
|
||||||
// todo - memoize this, if we ever need to optimize //
|
// todo - memoize this, if we ever need to optimize //
|
||||||
//////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////
|
||||||
FieldAndJoinTable fieldAndJoinTable = getFieldAndJoinTable(table, fieldName);
|
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, fieldName);
|
||||||
field = fieldAndJoinTable.field();
|
field = fieldAndJoinTable.field();
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
@ -956,7 +931,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
List<QFieldMetaData> fields = new ArrayList<>();
|
List<QFieldMetaData> fields = new ArrayList<>();
|
||||||
for(String summaryFieldName : view.getSummaryFields())
|
for(String summaryFieldName : view.getSummaryFields())
|
||||||
{
|
{
|
||||||
FieldAndJoinTable fieldAndJoinTable = getFieldAndJoinTable(table, summaryFieldName);
|
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, summaryFieldName);
|
||||||
fields.add(new QFieldMetaData(summaryFieldName, fieldAndJoinTable.field().getType()).withLabel(fieldAndJoinTable.field().getLabel())); // todo do we need the type? if so need table as input here
|
fields.add(new QFieldMetaData(summaryFieldName, fieldAndJoinTable.field().getType()).withLabel(fieldAndJoinTable.field().getLabel())); // todo do we need the type? if so need table as input here
|
||||||
}
|
}
|
||||||
for(QReportField column : view.getColumns())
|
for(QReportField column : view.getColumns())
|
||||||
@ -1208,27 +1183,4 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable)
|
|
||||||
{
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public String getLabel(QTableMetaData mainTable)
|
|
||||||
{
|
|
||||||
if(mainTable.getName().equals(joinTable.getName()))
|
|
||||||
{
|
|
||||||
return (field.getLabel());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return (joinTable.getLabel() + ": " + field.getLabel());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -533,10 +533,16 @@ public class QueryStatManager
|
|||||||
////////////////////////
|
////////////////////////
|
||||||
if(getOutput.getRecord() == null)
|
if(getOutput.getRecord() == null)
|
||||||
{
|
{
|
||||||
|
QTableMetaData tableMetaData = getInstance().qInstance.getTable(tableName);
|
||||||
|
if(tableMetaData == null)
|
||||||
|
{
|
||||||
|
LOG.info("No such table", logPair("tableName", tableName));
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
// insert the record (into the table, not the cache) //
|
// insert the record (into the table, not the cache) //
|
||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
QTableMetaData tableMetaData = getInstance().qInstance.getTable(tableName);
|
|
||||||
InsertInput insertInput = new InsertInput();
|
InsertInput insertInput = new InsertInput();
|
||||||
insertInput.setTableName(QQQTable.TABLE_NAME);
|
insertInput.setTableName(QQQTable.TABLE_NAME);
|
||||||
insertInput.setRecords(List.of(new QRecord().withValue("name", tableName).withValue("label", tableMetaData.getLabel())));
|
insertInput.setRecords(List.of(new QRecord().withValue("name", tableName).withValue("label", tableMetaData.getLabel())));
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public enum CriteriaOption implements CriteriaOptionInterface
|
||||||
|
{
|
||||||
|
CASE_INSENSITIVE;
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface CriteriaOptionInterface
|
||||||
|
{
|
||||||
|
}
|
@ -26,8 +26,10 @@ import java.io.Serializable;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.serialization.QFilterCriteriaDeserializer;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.serialization.QFilterCriteriaDeserializer;
|
||||||
@ -53,6 +55,8 @@ public class QFilterCriteria implements Serializable, Cloneable
|
|||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
private String otherFieldName;
|
private String otherFieldName;
|
||||||
|
|
||||||
|
private Set<CriteriaOptionInterface> options = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -69,6 +73,13 @@ public class QFilterCriteria implements Serializable, Cloneable
|
|||||||
clone.values = new ArrayList<>();
|
clone.values = new ArrayList<>();
|
||||||
clone.values.addAll(values);
|
clone.values.addAll(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(options != null)
|
||||||
|
{
|
||||||
|
clone.options = new HashSet<>();
|
||||||
|
clone.options.addAll(options);
|
||||||
|
}
|
||||||
|
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
catch(CloneNotSupportedException e)
|
catch(CloneNotSupportedException e)
|
||||||
@ -385,4 +396,78 @@ public class QFilterCriteria implements Serializable, Cloneable
|
|||||||
return Objects.hash(fieldName, operator, values, otherFieldName);
|
return Objects.hash(fieldName, operator, values, otherFieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for options
|
||||||
|
*******************************************************************************/
|
||||||
|
public Set<CriteriaOptionInterface> getOptions()
|
||||||
|
{
|
||||||
|
return (this.options);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for options
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setOptions(Set<CriteriaOptionInterface> options)
|
||||||
|
{
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for options
|
||||||
|
*******************************************************************************/
|
||||||
|
public QFilterCriteria withOptions(Set<CriteriaOptionInterface> options)
|
||||||
|
{
|
||||||
|
this.options = options;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public QFilterCriteria withOption(CriteriaOptionInterface option)
|
||||||
|
{
|
||||||
|
if(options == null)
|
||||||
|
{
|
||||||
|
options = new HashSet<>();
|
||||||
|
}
|
||||||
|
options.add(option);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public QFilterCriteria withoutOption(CriteriaOptionInterface option)
|
||||||
|
{
|
||||||
|
if(options != null)
|
||||||
|
{
|
||||||
|
options.remove(option);
|
||||||
|
}
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public boolean hasOption(CriteriaOptionInterface option)
|
||||||
|
{
|
||||||
|
if(options == null)
|
||||||
|
{
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (options.contains(option));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -853,4 +853,20 @@ public class QQueryFilter implements Serializable, Cloneable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public void applyCriteriaOptionToAllCriteria(CriteriaOptionInterface criteriaOption)
|
||||||
|
{
|
||||||
|
for(QFilterCriteria criteria : CollectionUtils.nonNullList(this.criteria))
|
||||||
|
{
|
||||||
|
criteria.withOption(criteriaOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(QQueryFilter subFilter : CollectionUtils.nonNullList(subFilters))
|
||||||
|
{
|
||||||
|
subFilter.applyCriteriaOptionToAllCriteria(criteriaOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ public class QRecord implements Serializable
|
|||||||
return (null);
|
return (null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, V> clone = new LinkedHashMap<>();
|
Map<String, V> clone = new LinkedHashMap<>(map.size());
|
||||||
for(Map.Entry<String, V> entry : map.entrySet())
|
for(Map.Entry<String, V> entry : map.entrySet())
|
||||||
{
|
{
|
||||||
Serializable value = entry.getValue();
|
Serializable value = entry.getValue();
|
||||||
@ -246,6 +246,24 @@ public class QRecord implements Serializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** copy all values from 'joinedRecord' into this record's values map,
|
||||||
|
** prefixing field names with joinTableNam + "."
|
||||||
|
***************************************************************************/
|
||||||
|
public void addJoinedRecordValues(String joinTableName, QRecord joinedRecord)
|
||||||
|
{
|
||||||
|
if(joinedRecord == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Map.Entry<String, Serializable> entry : joinedRecord.getValues().entrySet())
|
||||||
|
{
|
||||||
|
setValue(joinTableName + "." + entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
|
@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.data;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Extension on QRecord, intended to be used where you've got records from
|
||||||
|
** multiple tables, and you want to combine them into a single "wide" joined
|
||||||
|
** record - but to do so without copying or modifying any of the individual
|
||||||
|
** records.
|
||||||
|
**
|
||||||
|
** e.g., given:
|
||||||
|
** - Order (id, orderNo, orderDate) (main table)
|
||||||
|
** - LineItem (id, sku, quantity)
|
||||||
|
** - Extrinsic (id, key, value)
|
||||||
|
**
|
||||||
|
** If set up in here as:
|
||||||
|
** - new QRecordWithJoinedRecords(order)
|
||||||
|
** .withJoinedRecordValues(lineItem)
|
||||||
|
** .withJoinedRecordValues(extrinsic)
|
||||||
|
**
|
||||||
|
** Then we'd have the appearance of values in the object like:
|
||||||
|
** - id, orderNo, orderDate, lineItem.id, lineItem.sku, lineItem.quantity, extrinsic.id, extrinsic.key, extrinsic.value
|
||||||
|
**
|
||||||
|
** Which, by the by, is how a query that returns joined records looks, and, is
|
||||||
|
** what BackendQueryFilterUtils can use to do filter.
|
||||||
|
**
|
||||||
|
** This is done without copying or mutating any of the records (which, if you just use
|
||||||
|
** QRecord.withJoinedRecordValues, then those values are copied into the main record)
|
||||||
|
** - because this object is just storing references to the input records.
|
||||||
|
**
|
||||||
|
** Note that this implies that, values changed in this record (e.g, calls to setValue)
|
||||||
|
** WILL impact the underlying records!
|
||||||
|
*******************************************************************************/
|
||||||
|
public class QRecordWithJoinedRecords extends QRecord
|
||||||
|
{
|
||||||
|
private QRecord mainRecord;
|
||||||
|
private Map<String, QRecord> components = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public QRecordWithJoinedRecords(QRecord mainRecord)
|
||||||
|
{
|
||||||
|
this.mainRecord = mainRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void addJoinedRecordValues(String joinTableName, QRecord joinedRecord)
|
||||||
|
{
|
||||||
|
components.put(joinTableName, joinedRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public QRecordWithJoinedRecords withJoinedRecordValues(QRecord record, String joinTableName)
|
||||||
|
{
|
||||||
|
addJoinedRecordValues(joinTableName, record);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public Serializable getValue(String fieldName)
|
||||||
|
{
|
||||||
|
return performFunctionOnRecordBasedOnFieldName(fieldName, ((record, f) -> record.getValue(f)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
*
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void setValue(String fieldName, Object value)
|
||||||
|
{
|
||||||
|
performFunctionOnRecordBasedOnFieldName(fieldName, ((record, f) ->
|
||||||
|
{
|
||||||
|
record.setValue(f, value);
|
||||||
|
return (null);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
*
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void setValue(String fieldName, Serializable value)
|
||||||
|
{
|
||||||
|
performFunctionOnRecordBasedOnFieldName(fieldName, ((record, f) ->
|
||||||
|
{
|
||||||
|
record.setValue(f, value);
|
||||||
|
return (null);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void removeValue(String fieldName)
|
||||||
|
{
|
||||||
|
performFunctionOnRecordBasedOnFieldName(fieldName, ((record, f) ->
|
||||||
|
{
|
||||||
|
record.removeValue(f);
|
||||||
|
return (null);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** avoid having this same block in all the functions that call it...
|
||||||
|
** given a fieldName, which may be a joinTable.fieldName, apply the function
|
||||||
|
** to the right entity.
|
||||||
|
***************************************************************************/
|
||||||
|
private Serializable performFunctionOnRecordBasedOnFieldName(String fieldName, BiFunction<QRecord, String, Serializable> functionToPerform)
|
||||||
|
{
|
||||||
|
if(fieldName.contains("."))
|
||||||
|
{
|
||||||
|
String[] parts = fieldName.split("\\.");
|
||||||
|
QRecord component = components.get(parts[0]);
|
||||||
|
if(component != null)
|
||||||
|
{
|
||||||
|
return functionToPerform.apply(component, parts[1]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return functionToPerform.apply(mainRecord, fieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public Map<String, Serializable> getValues()
|
||||||
|
{
|
||||||
|
Map<String, Serializable> rs = new LinkedHashMap<>(mainRecord.getValues());
|
||||||
|
for(Map.Entry<String, QRecord> componentEntry : components.entrySet())
|
||||||
|
{
|
||||||
|
String joinTableName = componentEntry.getKey();
|
||||||
|
QRecord componentRecord = componentEntry.getValue();
|
||||||
|
for(Map.Entry<String, Serializable> entry : componentRecord.getValues().entrySet())
|
||||||
|
{
|
||||||
|
rs.put(joinTableName + "." + entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (rs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Wrapper (record) that holds a QFieldMetaData and a QTableMetaData -
|
||||||
|
**
|
||||||
|
** With a factory method (`get()`) to go from the use-case of, a String that's
|
||||||
|
** "joinTable.fieldName" or "fieldName" to the pair.
|
||||||
|
**
|
||||||
|
** Note that the "joinTable" member here - could be the "mainTable" passed in
|
||||||
|
** to that `get()` method.
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable)
|
||||||
|
{
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** given a table, and a field-name string (which should either be the name
|
||||||
|
** of a field on that table, or another tableName + "." + fieldName (from
|
||||||
|
** that table) - get back the pair of table & field metaData that the
|
||||||
|
** input string is talking about.
|
||||||
|
***************************************************************************/
|
||||||
|
public static FieldAndJoinTable get(QTableMetaData mainTable, String fieldName) throws QException
|
||||||
|
{
|
||||||
|
if(fieldName.indexOf('.') > -1)
|
||||||
|
{
|
||||||
|
String joinTableName = fieldName.replaceAll("\\..*", "");
|
||||||
|
String joinFieldName = fieldName.replaceAll(".*\\.", "");
|
||||||
|
|
||||||
|
QTableMetaData joinTable = QContext.getQInstance().getTable(joinTableName);
|
||||||
|
if(joinTable == null)
|
||||||
|
{
|
||||||
|
throw (new QException("Unrecognized join table name: " + joinTableName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FieldAndJoinTable(joinTable.getField(joinFieldName), joinTable);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new FieldAndJoinTable(mainTable.getField(fieldName), mainTable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getLabel(QTableMetaData mainTable)
|
||||||
|
{
|
||||||
|
if(mainTable.getName().equals(joinTable.getName()))
|
||||||
|
{
|
||||||
|
return (field.getLabel());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (joinTable.getLabel() + ": " + field.getLabel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,6 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
@ -43,6 +42,7 @@ import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.DynamicFormWidgetData;
|
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.DynamicFormWidgetData;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAndJoinTable;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.SavedReportToReportMetaDataAdapter;
|
import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.SavedReportToReportMetaDataAdapter;
|
||||||
@ -127,7 +127,7 @@ public class ReportValuesDynamicFormWidgetRenderer extends AbstractWidgetRendere
|
|||||||
{
|
{
|
||||||
if(criteriaValue instanceof FilterVariableExpression filterVariableExpression)
|
if(criteriaValue instanceof FilterVariableExpression filterVariableExpression)
|
||||||
{
|
{
|
||||||
GenerateReportAction.FieldAndJoinTable fieldAndJoinTable = GenerateReportAction.getFieldAndJoinTable(table, criteria.getFieldName());
|
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, criteria.getFieldName());
|
||||||
QFieldMetaData fieldMetaData = fieldAndJoinTable.field().clone();
|
QFieldMetaData fieldMetaData = fieldAndJoinTable.field().clone();
|
||||||
|
|
||||||
/////////////////////////////////
|
/////////////////////////////////
|
||||||
|
@ -28,7 +28,6 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction;
|
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition;
|
||||||
@ -39,6 +38,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAndJoinTable;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage;
|
import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage;
|
||||||
import com.kingsrook.qqq.backend.core.model.statusmessages.PermissionDeniedMessage;
|
import com.kingsrook.qqq.backend.core.model.statusmessages.PermissionDeniedMessage;
|
||||||
@ -311,7 +311,7 @@ public class SavedReportTableCustomizer implements TableCustomizerInterface
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
GenerateReportAction.FieldAndJoinTable fieldAndJoinTable = GenerateReportAction.getFieldAndJoinTable(table, fieldName);
|
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, fieldName);
|
||||||
return (fieldAndJoinTable.getLabel(table));
|
return (fieldAndJoinTable.getLabel(table));
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
|
@ -34,6 +34,7 @@ import java.util.Objects;
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.CriteriaOption;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
@ -268,6 +269,11 @@ public class BackendQueryFilterUtils
|
|||||||
|
|
||||||
String regex = sqlLikeToRegex(criterionValue);
|
String regex = sqlLikeToRegex(criterionValue);
|
||||||
|
|
||||||
|
if(criterion.hasOption(CriteriaOption.CASE_INSENSITIVE))
|
||||||
|
{
|
||||||
|
return (stringValue.toLowerCase().matches(regex.toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
return (stringValue.matches(regex));
|
return (stringValue.matches(regex));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,6 +433,23 @@ public class BackendQueryFilterUtils
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(criterion.hasOption(CriteriaOption.CASE_INSENSITIVE))
|
||||||
|
{
|
||||||
|
if(CollectionUtils.nullSafeHasContents(criterion.getValues()))
|
||||||
|
{
|
||||||
|
if(criterion.getValues().get(0) instanceof String)
|
||||||
|
{
|
||||||
|
for(Serializable criterionValue : criterion.getValues())
|
||||||
|
{
|
||||||
|
if(criterionValue instanceof String criterionValueString && value instanceof String valueString && criterionValueString.equalsIgnoreCase(valueString))
|
||||||
|
{
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(value == null || !criterion.getValues().contains(value))
|
if(value == null || !criterion.getValues().contains(value))
|
||||||
{
|
{
|
||||||
return (false);
|
return (false);
|
||||||
@ -456,6 +479,14 @@ public class BackendQueryFilterUtils
|
|||||||
value = String.valueOf(value);
|
value = String.valueOf(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(criterion.hasOption(CriteriaOption.CASE_INSENSITIVE))
|
||||||
|
{
|
||||||
|
if(value instanceof String valueString && criteriaValue instanceof String criteriaValueString && valueString.equalsIgnoreCase(criteriaValueString))
|
||||||
|
{
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!value.equals(criteriaValue))
|
if(!value.equals(criteriaValue))
|
||||||
{
|
{
|
||||||
return (false);
|
return (false);
|
||||||
@ -473,6 +504,14 @@ public class BackendQueryFilterUtils
|
|||||||
String stringValue = getStringFieldValue(value, fieldName, criterion);
|
String stringValue = getStringFieldValue(value, fieldName, criterion);
|
||||||
String criterionValue = getFirstStringCriterionValue(criterion);
|
String criterionValue = getFirstStringCriterionValue(criterion);
|
||||||
|
|
||||||
|
if(criterion.hasOption(CriteriaOption.CASE_INSENSITIVE))
|
||||||
|
{
|
||||||
|
if(stringValue.toLowerCase().contains(criterionValue.toLowerCase()))
|
||||||
|
{
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!stringValue.contains(criterionValue))
|
if(!stringValue.contains(criterionValue))
|
||||||
{
|
{
|
||||||
return (false);
|
return (false);
|
||||||
@ -491,6 +530,14 @@ public class BackendQueryFilterUtils
|
|||||||
String stringValue = getStringFieldValue(value, fieldName, criterion);
|
String stringValue = getStringFieldValue(value, fieldName, criterion);
|
||||||
String criterionValue = getFirstStringCriterionValue(criterion);
|
String criterionValue = getFirstStringCriterionValue(criterion);
|
||||||
|
|
||||||
|
if(criterion.hasOption(CriteriaOption.CASE_INSENSITIVE))
|
||||||
|
{
|
||||||
|
if(stringValue.toLowerCase().startsWith(criterionValue.toLowerCase()))
|
||||||
|
{
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!stringValue.startsWith(criterionValue))
|
if(!stringValue.startsWith(criterionValue))
|
||||||
{
|
{
|
||||||
return (false);
|
return (false);
|
||||||
@ -509,6 +556,14 @@ public class BackendQueryFilterUtils
|
|||||||
String stringValue = getStringFieldValue(value, fieldName, criterion);
|
String stringValue = getStringFieldValue(value, fieldName, criterion);
|
||||||
String criterionValue = getFirstStringCriterionValue(criterion);
|
String criterionValue = getFirstStringCriterionValue(criterion);
|
||||||
|
|
||||||
|
if(criterion.hasOption(CriteriaOption.CASE_INSENSITIVE))
|
||||||
|
{
|
||||||
|
if(stringValue.toLowerCase().endsWith(criterionValue.toLowerCase()))
|
||||||
|
{
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!stringValue.endsWith(criterionValue))
|
if(!stringValue.endsWith(criterionValue))
|
||||||
{
|
{
|
||||||
return (false);
|
return (false);
|
||||||
@ -665,4 +720,5 @@ public class BackendQueryFilterUtils
|
|||||||
regex.append("$");
|
regex.append("$");
|
||||||
return regex.toString();
|
return regex.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -291,4 +291,24 @@ class QRecordTest extends BaseTest
|
|||||||
assertFalse(jsonObject.has("errorsAsString"));
|
assertFalse(jsonObject.has("errorsAsString"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testAddJoinedRecordValues()
|
||||||
|
{
|
||||||
|
QRecord order = new QRecord().withValue("id", 1).withValue("shipTo", "St. Louis");
|
||||||
|
order.addJoinedRecordValues("orderInstructions", null);
|
||||||
|
assertEquals(2, order.getValues().size());
|
||||||
|
|
||||||
|
QRecord orderInstructions = new QRecord().withValue("id", 100).withValue("instructions", "Be Careful");
|
||||||
|
order.addJoinedRecordValues("orderInstructions", orderInstructions);
|
||||||
|
|
||||||
|
assertEquals(4, order.getValues().size());
|
||||||
|
assertEquals(100, order.getValue("orderInstructions.id"));
|
||||||
|
assertEquals("Be Careful", order.getValue("orderInstructions.instructions"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.data;
|
||||||
|
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.Month;
|
||||||
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for QRecordWithJoinedRecords
|
||||||
|
*******************************************************************************/
|
||||||
|
class QRecordWithJoinedRecordsTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void test()
|
||||||
|
{
|
||||||
|
QRecord order = new QRecord().withValue("id", 1).withValue("orderNo", "101").withValue("orderDate", LocalDate.of(2025, Month.JANUARY, 1));
|
||||||
|
QRecord lineItem = new QRecord().withValue("id", 2).withValue("sku", "ABC").withValue("quantity", 47);
|
||||||
|
QRecord extrinsic = new QRecord().withValue("id", 3).withValue("key", "MyKey").withValue("value", "MyValue");
|
||||||
|
|
||||||
|
QRecordWithJoinedRecords joinedRecords = new QRecordWithJoinedRecords(order);
|
||||||
|
joinedRecords.addJoinedRecordValues("lineItem", lineItem);
|
||||||
|
joinedRecords.addJoinedRecordValues("extrinsic", extrinsic);
|
||||||
|
|
||||||
|
assertEquals(1, joinedRecords.getValue("id"));
|
||||||
|
assertEquals("101", joinedRecords.getValue("orderNo"));
|
||||||
|
assertEquals(LocalDate.of(2025, Month.JANUARY, 1), joinedRecords.getValue("orderDate"));
|
||||||
|
assertEquals(2, joinedRecords.getValue("lineItem.id"));
|
||||||
|
assertEquals("ABC", joinedRecords.getValue("lineItem.sku"));
|
||||||
|
assertEquals(47, joinedRecords.getValue("lineItem.quantity"));
|
||||||
|
assertEquals(3, joinedRecords.getValue("extrinsic.id"));
|
||||||
|
assertEquals("MyKey", joinedRecords.getValue("extrinsic.key"));
|
||||||
|
assertEquals("MyValue", joinedRecords.getValue("extrinsic.value"));
|
||||||
|
|
||||||
|
assertEquals(9, joinedRecords.getValues().size());
|
||||||
|
assertEquals(1, joinedRecords.getValues().get("id"));
|
||||||
|
assertEquals(2, joinedRecords.getValues().get("lineItem.id"));
|
||||||
|
assertEquals(3, joinedRecords.getValues().get("extrinsic.id"));
|
||||||
|
|
||||||
|
joinedRecords.setValue("lineItem.color", "RED");
|
||||||
|
assertEquals("RED", joinedRecords.getValue("lineItem.color"));
|
||||||
|
assertEquals("RED", lineItem.getValue("color"));
|
||||||
|
|
||||||
|
joinedRecords.setValue("shipToCity", "St. Louis");
|
||||||
|
assertEquals("St. Louis", joinedRecords.getValue("shipToCity"));
|
||||||
|
assertEquals("St. Louis", order.getValue("shipToCity"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,8 +22,10 @@
|
|||||||
package com.kingsrook.qqq.backend.core.modules.backend.implementations.utils;
|
package com.kingsrook.qqq.backend.core.modules.backend.implementations.utils;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.CriteriaOption;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
@ -305,6 +307,73 @@ class BackendQueryFilterUtilsTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
private QFilterCriteria newCaseInsensitiveCriteria(String fieldName, QCriteriaOperator operator, Serializable... values)
|
||||||
|
{
|
||||||
|
return new QFilterCriteria(fieldName, operator, values).withOption(CriteriaOption.CASE_INSENSITIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
private QFilterCriteria newCaseInsensitiveCriteria(String fieldName, QCriteriaOperator operator, List<Serializable> values)
|
||||||
|
{
|
||||||
|
return new QFilterCriteria(fieldName, operator, values).withOption(CriteriaOption.CASE_INSENSITIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testDoesCriterionMatchCaseInsensitive()
|
||||||
|
{
|
||||||
|
////////////////
|
||||||
|
// like & not //
|
||||||
|
////////////////
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.LIKE, "Test"), "f", "test"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.LIKE, "test"), "f", "Test"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.LIKE, "T%"), "f", "test"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.LIKE, "t%"), "f", "Test"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.LIKE, "T_st"), "f", "test"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.LIKE, "t_st"), "f", "Test"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.NOT_LIKE, "Test"), "f", "Tst"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.NOT_LIKE, "Test"), "f", "tst"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.NOT_LIKE, "T%"), "f", "Rest"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.NOT_LIKE, "T_st"), "f", "Toast"));
|
||||||
|
|
||||||
|
//////////////
|
||||||
|
// IN & NOT //
|
||||||
|
//////////////
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.IN, "A"), "f", "a"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.IN, "a"), "f", "A"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.IN, "A", "B"), "f", "a"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.IN, "A", "b"), "f", "B"));
|
||||||
|
assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.IN, List.of()), "f", "A"));
|
||||||
|
assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.IN, ListBuilder.of(null)), "f", "A"));
|
||||||
|
|
||||||
|
assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.NOT_IN, "A"), "f", "A"));
|
||||||
|
assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.NOT_IN, "A", "B"), "f", "a"));
|
||||||
|
assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.NOT_IN, "A", "b"), "f", "B"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.NOT_IN, List.of()), "f", "A"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.NOT_IN, ListBuilder.of(null)), "f", "A"));
|
||||||
|
|
||||||
|
///////////////////////////
|
||||||
|
// NOT_EQUALS_OR_IS_NULL //
|
||||||
|
///////////////////////////
|
||||||
|
assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.NOT_EQUALS_OR_IS_NULL, "A"), "f", "A"));
|
||||||
|
assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.NOT_EQUALS_OR_IS_NULL, "A"), "f", "a"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.NOT_EQUALS_OR_IS_NULL, "A"), "f", "B"));
|
||||||
|
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(newCaseInsensitiveCriteria("f", QCriteriaOperator.NOT_EQUALS_OR_IS_NULL, "A"), "f", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
Reference in New Issue
Block a user