mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Merge pull request #6 from Kingsrook/feature/sprint-9-support-updates
Feature/sprint 9 support updates
This commit is contained in:
8
pom.xml
8
pom.xml
@ -48,6 +48,7 @@
|
||||
<maven.compiler.showWarnings>true</maven.compiler.showWarnings>
|
||||
<coverage.haltOnFailure>true</coverage.haltOnFailure>
|
||||
<coverage.instructionCoveredRatioMinimum>0.80</coverage.instructionCoveredRatioMinimum>
|
||||
<coverage.classCoveredRatioMinimum>0.95</coverage.classCoveredRatioMinimum>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@ -211,6 +212,11 @@
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>${coverage.instructionCoveredRatioMinimum}</minimum>
|
||||
</limit>
|
||||
<limit>
|
||||
<counter>CLASS</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>${coverage.classCoveredRatioMinimum}</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
</rules>
|
||||
@ -255,7 +261,7 @@ echo "------------------------------------------------------------"
|
||||
which xpath > /dev/null 2>&1
|
||||
if [ "$?" == "0" ]; then
|
||||
echo "Element\nInstructions Missed\nInstruction Coverage\nBranches Missed\nBranch Coverage\nComplexity Missed\nComplexity Hit\nLines Missed\nLines Hit\nMethods Missed\nMethods Hit\nClasses Missed\nClasses Hit\n" > /tmp/$$.headers
|
||||
xpath -q -e '/html/body/table/tfoot/tr[1]/td/text()' target/site/jacoco/index.html > /tmp/$$.values
|
||||
xpath -n -q -e '/html/body/table/tfoot/tr[1]/td/text()' target/site/jacoco/index.html > /tmp/$$.values
|
||||
paste /tmp/$$.headers /tmp/$$.values | tail +2 | awk -v FS='\t' '{printf("%-20s %s\n",$1,$2)}'
|
||||
rm /tmp/$$.headers /tmp/$$.values
|
||||
else
|
||||
|
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.actions.customizers;
|
||||
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility to load code for running QQQ customizers.
|
||||
*******************************************************************************/
|
||||
public class QCodeLoader
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QCodeLoader.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static <T, R> Optional<Function<T, R>> getTableCustomizerFunction(QTableMetaData table, String customizerName)
|
||||
{
|
||||
Optional<QCodeReference> codeReference = table.getCustomizer(customizerName);
|
||||
if(codeReference.isPresent())
|
||||
{
|
||||
return (Optional.ofNullable(QCodeLoader.getFunction(codeReference.get())));
|
||||
}
|
||||
return (Optional.empty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, R> Function<T, R> getFunction(QCodeReference codeReference)
|
||||
{
|
||||
if(codeReference == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
throw (new IllegalArgumentException("Only JAVA customizers are supported at this time."));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Class<?> customizerClass = Class.forName(codeReference.getName());
|
||||
return ((Function<T, R>) customizerClass.getConstructor().newInstance());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error initializing customizer: " + codeReference);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
||||
// as we'll want to validate all functions in the instance validator at startup time (and IT will throw //
|
||||
// if it finds an invalid code reference //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QCustomPossibleValueProvider getCustomPossibleValueProvider(QPossibleValueSource possibleValueSource) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> codeClass = Class.forName(possibleValueSource.getCustomCodeReference().getName());
|
||||
Object codeObject = codeClass.getConstructor().newInstance();
|
||||
if(!(codeObject instanceof QCustomPossibleValueProvider customPossibleValueProvider))
|
||||
{
|
||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of QCustomPossibleValueProvider"));
|
||||
}
|
||||
return (customPossibleValueProvider);
|
||||
}
|
||||
catch(QException qe)
|
||||
{
|
||||
throw (qe);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error getting custom possible value provider for PVS [" + possibleValueSource.getName() + "]", e));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Object used by TableCustomizers enum (and similar enums in backend modules)
|
||||
** to assist with definition and validation of Customizers applied to tables.
|
||||
*******************************************************************************/
|
||||
public class TableCustomizer
|
||||
{
|
||||
private final String role;
|
||||
private final Class<?> expectedType;
|
||||
private final Consumer<Object> validationFunction;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableCustomizer(String role, Class<?> expectedType, Consumer<Object> validationFunction)
|
||||
{
|
||||
this.role = role;
|
||||
this.expectedType = expectedType;
|
||||
this.validationFunction = validationFunction;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for role
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getRole()
|
||||
{
|
||||
return role;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for expectedType
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Class<?> getExpectedType()
|
||||
{
|
||||
return expectedType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for validationFunction
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Consumer<Object> getValidationFunction()
|
||||
{
|
||||
return validationFunction;
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Enum definition of possible table customizers - "roles" for custom code that
|
||||
** can be applied to tables.
|
||||
**
|
||||
** Works with TableCustomizer (singular version of this name) objects, during
|
||||
** instance validation, to provide validation of the referenced code (and to
|
||||
** make such validation from sub-backend-modules possible in the future).
|
||||
**
|
||||
** The idea of the 3rd argument here is to provide a way that we can enforce
|
||||
** the type-parameters for the custom code. E.g., if it's a Function - how
|
||||
** can we check at run-time that the type-params are correct? We couldn't find
|
||||
** how to do this "reflectively", so we can instead try to run the custom code,
|
||||
** passing it objects of the type that this customizer expects, and a validation
|
||||
** error will raise upon ClassCastException... This maybe could improve!
|
||||
*******************************************************************************/
|
||||
public enum TableCustomizers
|
||||
{
|
||||
POST_QUERY_RECORD(new TableCustomizer("postQueryRecord", Function.class, ((Object x) ->
|
||||
{
|
||||
Function<QRecord, QRecord> function = (Function<QRecord, QRecord>) x;
|
||||
QRecord output = function.apply(new QRecord());
|
||||
})));
|
||||
|
||||
|
||||
private final TableCustomizer tableCustomizer;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
TableCustomizers(TableCustomizer tableCustomizer)
|
||||
{
|
||||
this.tableCustomizer = tableCustomizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Get the TableCustomer for a given role (e.g., the role used in meta-data, not
|
||||
** the enum-constant name).
|
||||
*******************************************************************************/
|
||||
public static TableCustomizers forRole(String name)
|
||||
{
|
||||
for(TableCustomizers value : values())
|
||||
{
|
||||
if(value.tableCustomizer.getRole().equals(name))
|
||||
{
|
||||
return (value);
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableCustomizer
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableCustomizer getTableCustomizer()
|
||||
{
|
||||
return tableCustomizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** get the role from the tableCustomizer
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getRole()
|
||||
{
|
||||
return (tableCustomizer.getRole());
|
||||
}
|
||||
|
||||
}
|
@ -215,7 +215,7 @@ public class RunBackendStepAction
|
||||
Object codeObject = codeClass.getConstructor().newInstance();
|
||||
if(!(codeObject instanceof BackendStep backendStepCodeObject))
|
||||
{
|
||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of FunctionBody"));
|
||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of BackendStep"));
|
||||
}
|
||||
|
||||
backendStepCodeObject.run(runBackendStepInput, runBackendStepOutput);
|
||||
|
@ -26,6 +26,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@ -42,12 +43,42 @@ public class RecordPipe
|
||||
|
||||
private ArrayBlockingQueue<QRecord> queue = new ArrayBlockingQueue<>(1_000);
|
||||
|
||||
private Consumer<List<QRecord>> postRecordActions = null;
|
||||
|
||||
/////////////////////////////////////
|
||||
// See usage below for explanation //
|
||||
/////////////////////////////////////
|
||||
private List<QRecord> singleRecordListForPostRecordActions = new ArrayList<>();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Add a record to the pipe
|
||||
** Returns true iff the record fit in the pipe; false if the pipe is currently full.
|
||||
** Add a record to the pipe. Will block if the pipe is full.
|
||||
*******************************************************************************/
|
||||
public void addRecord(QRecord record)
|
||||
{
|
||||
if(postRecordActions != null)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// the initial use-case of this method is to call QueryAction.postRecordActions //
|
||||
// that method requires that the list param be modifiable. Originally we used //
|
||||
// List.of here - but that is immutable, so, instead use this single-record-list //
|
||||
// (which we'll create as a field in this class, to avoid always re-constructing) //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
singleRecordListForPostRecordActions.add(record);
|
||||
postRecordActions.accept(singleRecordListForPostRecordActions);
|
||||
record = singleRecordListForPostRecordActions.remove(0);
|
||||
}
|
||||
|
||||
doAddRecord(record);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Private internal version of add record - assumes the postRecordActions have
|
||||
** already ran.
|
||||
*******************************************************************************/
|
||||
private void doAddRecord(QRecord record)
|
||||
{
|
||||
boolean offerResult = queue.offer(record);
|
||||
|
||||
@ -66,7 +97,15 @@ public class RecordPipe
|
||||
*******************************************************************************/
|
||||
public void addRecords(List<QRecord> records)
|
||||
{
|
||||
records.forEach(this::addRecord);
|
||||
if(postRecordActions != null)
|
||||
{
|
||||
postRecordActions.accept(records);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// make sure to go to the private version of doAddRecord - to avoid re-running the post-actions //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
records.forEach(this::doAddRecord);
|
||||
}
|
||||
|
||||
|
||||
@ -101,4 +140,14 @@ public class RecordPipe
|
||||
return (queue.size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setPostRecordActions(Consumer<List<QRecord>> postRecordActions)
|
||||
{
|
||||
this.postRecordActions = postRecordActions;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,11 +22,18 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
|
||||
@ -37,6 +44,14 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
*******************************************************************************/
|
||||
public class QueryAction
|
||||
{
|
||||
private Optional<Function<QRecord, QRecord>> postQueryRecordCustomizer;
|
||||
|
||||
private QueryInput queryInput;
|
||||
private QValueFormatter qValueFormatter;
|
||||
private QPossibleValueTranslator qPossibleValueTranslator;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -44,18 +59,58 @@ public class QueryAction
|
||||
{
|
||||
ActionHelper.validateSession(queryInput);
|
||||
|
||||
postQueryRecordCustomizer = QCodeLoader.getTableCustomizerFunction(queryInput.getTable(), TableCustomizers.POST_QUERY_RECORD.getRole());
|
||||
this.queryInput = queryInput;
|
||||
|
||||
if(queryInput.getRecordPipe() != null)
|
||||
{
|
||||
queryInput.getRecordPipe().setPostRecordActions(this::postRecordActions);
|
||||
}
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(queryInput.getBackend());
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(queryInput.getBackend());
|
||||
// todo pre-customization - just get to modify the request?
|
||||
QueryOutput queryOutput = qModule.getQueryInterface().execute(queryInput);
|
||||
// todo post-customization - can do whatever w/ the result if you want
|
||||
|
||||
if (queryInput.getRecordPipe() == null)
|
||||
if(queryInput.getRecordPipe() == null)
|
||||
{
|
||||
QValueFormatter.setDisplayValuesInRecords(queryInput.getTable(), queryOutput.getRecords());
|
||||
postRecordActions(queryOutput.getRecords());
|
||||
}
|
||||
|
||||
return queryOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Run the necessary actions on a list of records (which must be a mutable list - e.g.,
|
||||
** not one created via List.of()). This may include setting display values,
|
||||
** translating possible values, and running post-record customizations.
|
||||
*******************************************************************************/
|
||||
public void postRecordActions(List<QRecord> records)
|
||||
{
|
||||
if(this.postQueryRecordCustomizer.isPresent())
|
||||
{
|
||||
records.replaceAll(t -> postQueryRecordCustomizer.get().apply(t));
|
||||
}
|
||||
|
||||
if(queryInput.getShouldGenerateDisplayValues())
|
||||
{
|
||||
if(qValueFormatter == null)
|
||||
{
|
||||
qValueFormatter = new QValueFormatter();
|
||||
}
|
||||
qValueFormatter.setDisplayValuesInRecords(queryInput.getTable(), records);
|
||||
}
|
||||
|
||||
if(queryInput.getShouldTranslatePossibleValues())
|
||||
{
|
||||
if(qPossibleValueTranslator == null)
|
||||
{
|
||||
qPossibleValueTranslator = new QPossibleValueTranslator(queryInput.getInstance(), queryInput.getSession());
|
||||
}
|
||||
qPossibleValueTranslator.translatePossibleValuesInRecords(queryInput.getTable(), records);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.actions.values;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface to be implemented by user-defined code that serves as the backing
|
||||
** for a CUSTOM type possibleValueSource
|
||||
*******************************************************************************/
|
||||
public interface QCustomPossibleValueProvider
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
QPossibleValue<?> getPossibleValue(Serializable idValue);
|
||||
|
||||
// todo - get/search list of possible values
|
||||
|
||||
}
|
@ -0,0 +1,355 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.actions.values;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
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.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Class responsible for looking up possible-values for fields/records and
|
||||
** make them into display values.
|
||||
*******************************************************************************/
|
||||
public class QPossibleValueTranslator
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QPossibleValueTranslator.class);
|
||||
|
||||
private final QInstance qInstance;
|
||||
private final QSession session;
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// top-level keys are pvsNames (not table names) //
|
||||
// 2nd-level keys are pkey values from the PVS table //
|
||||
///////////////////////////////////////////////////////
|
||||
private Map<String, Map<Serializable, String>> possibleValueCache;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueTranslator(QInstance qInstance, QSession session)
|
||||
{
|
||||
this.qInstance = qInstance;
|
||||
this.session = session;
|
||||
|
||||
this.possibleValueCache = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a list of records, translate their possible values (populating their display values)
|
||||
*******************************************************************************/
|
||||
public void translatePossibleValuesInRecords(QTableMetaData table, List<QRecord> records)
|
||||
{
|
||||
if(records == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
primePvsCache(table, records);
|
||||
|
||||
for(QRecord record : records)
|
||||
{
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
if(field.getPossibleValueSourceName() != null)
|
||||
{
|
||||
record.setDisplayValue(field.getName(), translatePossibleValue(field, record.getValue(field.getName())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String translatePossibleValue(QFieldMetaData field, Serializable value)
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
||||
if(possibleValueSource == null)
|
||||
{
|
||||
LOG.error("Missing possible value source named [" + field.getPossibleValueSourceName() + "] when formatting value for field [" + field.getName() + "]");
|
||||
return (null);
|
||||
}
|
||||
|
||||
String resultValue = null;
|
||||
if(possibleValueSource.getType().equals(QPossibleValueSourceType.ENUM))
|
||||
{
|
||||
resultValue = translatePossibleValueEnum(value, possibleValueSource);
|
||||
}
|
||||
else if(possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE))
|
||||
{
|
||||
resultValue = translatePossibleValueTable(field, value, possibleValueSource);
|
||||
}
|
||||
else if(possibleValueSource.getType().equals(QPossibleValueSourceType.CUSTOM))
|
||||
{
|
||||
resultValue = translatePossibleValueCustom(field, value, possibleValueSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.error("Unrecognized possibleValueSourceType [" + possibleValueSource.getType() + "] in PVS named [" + possibleValueSource.getName() + "] on field [" + field.getName() + "]");
|
||||
}
|
||||
|
||||
if(resultValue == null)
|
||||
{
|
||||
resultValue = getDefaultForPossibleValue(possibleValueSource, value);
|
||||
}
|
||||
|
||||
return (resultValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String translatePossibleValueEnum(Serializable value, QPossibleValueSource possibleValueSource)
|
||||
{
|
||||
for(QPossibleValue<?> possibleValue : possibleValueSource.getEnumValues())
|
||||
{
|
||||
if(possibleValue.getId().equals(value))
|
||||
{
|
||||
return (formatPossibleValue(possibleValueSource, possibleValue));
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String translatePossibleValueTable(QFieldMetaData field, Serializable value, QPossibleValueSource possibleValueSource)
|
||||
{
|
||||
/////////////////////////////////
|
||||
// null input gets null output //
|
||||
/////////////////////////////////
|
||||
if(value == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// look for cached value - if it's missing, call the primer //
|
||||
//////////////////////////////////////////////////////////////
|
||||
possibleValueCache.putIfAbsent(possibleValueSource.getName(), new HashMap<>());
|
||||
Map<Serializable, String> cacheForPvs = possibleValueCache.get(possibleValueSource.getName());
|
||||
if(!cacheForPvs.containsKey(value))
|
||||
{
|
||||
primePvsCache(possibleValueSource.getTableName(), List.of(possibleValueSource), List.of(value));
|
||||
}
|
||||
|
||||
return (cacheForPvs.get(value));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String translatePossibleValueCustom(QFieldMetaData field, Serializable value, QPossibleValueSource possibleValueSource)
|
||||
{
|
||||
try
|
||||
{
|
||||
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
||||
return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value)));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error sending [" + value + "] for field [" + field + "] through custom code for PVS [" + field.getPossibleValueSourceName() + "]", e);
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String formatPossibleValue(QPossibleValueSource possibleValueSource, QPossibleValue<?> possibleValue)
|
||||
{
|
||||
return (doFormatPossibleValue(possibleValueSource.getValueFormat(), possibleValueSource.getValueFields(), possibleValue.getId(), possibleValue.getLabel()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String getDefaultForPossibleValue(QPossibleValueSource possibleValueSource, Serializable value)
|
||||
{
|
||||
if(possibleValueSource.getValueFormatIfNotFound() == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
return (doFormatPossibleValue(possibleValueSource.getValueFormatIfNotFound(), possibleValueSource.getValueFieldsIfNotFound(), value, null));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:Indentation")
|
||||
private String doFormatPossibleValue(String formatString, List<String> valueFields, Object id, String label)
|
||||
{
|
||||
List<Object> values = new ArrayList<>();
|
||||
if(valueFields != null)
|
||||
{
|
||||
for(String valueField : valueFields)
|
||||
{
|
||||
Object value = switch(valueField)
|
||||
{
|
||||
case "id" -> id;
|
||||
case "label" -> label;
|
||||
default -> throw new IllegalArgumentException("Unexpected value field: " + valueField);
|
||||
};
|
||||
values.add(Objects.requireNonNullElse(value, ""));
|
||||
}
|
||||
}
|
||||
|
||||
return (formatString.formatted(values.toArray()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** prime the cache (e.g., by doing bulk-queries) for table-based PVS's
|
||||
**
|
||||
** @param table the table that the records are from
|
||||
** @param records the records that have the possible value id's (e.g., foreign keys)
|
||||
*******************************************************************************/
|
||||
void primePvsCache(QTableMetaData table, List<QRecord> records)
|
||||
{
|
||||
ListingHash<String, QFieldMetaData> fieldsByPvsTable = new ListingHash<>();
|
||||
ListingHash<String, QPossibleValueSource> pvsesByTable = new ListingHash<>();
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
||||
if(possibleValueSource != null && possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE))
|
||||
{
|
||||
fieldsByPvsTable.add(possibleValueSource.getTableName(), field);
|
||||
pvsesByTable.add(possibleValueSource.getTableName(), possibleValueSource);
|
||||
}
|
||||
}
|
||||
|
||||
for(String tableName : fieldsByPvsTable.keySet())
|
||||
{
|
||||
Set<Serializable> values = new HashSet<>();
|
||||
for(QRecord record : records)
|
||||
{
|
||||
for(QFieldMetaData field : fieldsByPvsTable.get(tableName))
|
||||
{
|
||||
values.add(record.getValue(field.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
primePvsCache(tableName, pvsesByTable.get(tableName), values);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a given table, and a list of pkey-values in that table, AND a list of
|
||||
** possible value sources based on that table (maybe usually 1, but could be more,
|
||||
** e.g., if they had different formatting, or different filters (todo, would that work?)
|
||||
** - query for the values in the table, and populate the possibleValueCache.
|
||||
*******************************************************************************/
|
||||
private void primePvsCache(String tableName, List<QPossibleValueSource> possibleValueSources, Collection<Serializable> values)
|
||||
{
|
||||
for(QPossibleValueSource possibleValueSource : possibleValueSources)
|
||||
{
|
||||
possibleValueCache.putIfAbsent(possibleValueSource.getName(), new HashMap<>());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
String primaryKeyField = qInstance.getTable(tableName).getPrimaryKeyField();
|
||||
|
||||
for(List<Serializable> page : CollectionUtils.getPages(values, 1000))
|
||||
{
|
||||
QueryInput queryInput = new QueryInput(qInstance);
|
||||
queryInput.setSession(session);
|
||||
queryInput.setTableName(tableName);
|
||||
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(primaryKeyField, QCriteriaOperator.IN, page)));
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this is needed to get record labels, which are what we use here... unclear if best! //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
Serializable pkeyValue = record.getValue(primaryKeyField);
|
||||
for(QPossibleValueSource possibleValueSource : possibleValueSources)
|
||||
{
|
||||
QPossibleValue<?> possibleValue = new QPossibleValue<>(pkeyValue, record.getRecordLabel());
|
||||
possibleValueCache.get(possibleValueSource.getName()).put(pkeyValue, formatPossibleValue(possibleValueSource, possibleValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error looking up possible values for table [" + tableName + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -34,7 +34,8 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility to apply display formats to values for fields
|
||||
** Utility to apply display formats to values for records and fields.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QValueFormatter
|
||||
{
|
||||
@ -45,7 +46,7 @@ public class QValueFormatter
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String formatValue(QFieldMetaData field, Serializable value)
|
||||
public String formatValue(QFieldMetaData field, Serializable value)
|
||||
{
|
||||
//////////////////////////////////
|
||||
// null values get null results //
|
||||
@ -68,6 +69,7 @@ public class QValueFormatter
|
||||
{
|
||||
try
|
||||
{
|
||||
// todo - revisit if we actually want this - or - if you should get an error if you mis-configure your table this way (ideally during validation!)
|
||||
if(e.getMessage().equals("f != java.lang.Integer"))
|
||||
{
|
||||
return formatValue(field, ValueUtils.getValueAsBigDecimal(value));
|
||||
@ -99,7 +101,7 @@ public class QValueFormatter
|
||||
/*******************************************************************************
|
||||
** Make a string from a table's recordLabelFormat and fields, for a given record.
|
||||
*******************************************************************************/
|
||||
public static String formatRecordLabel(QTableMetaData table, QRecord record)
|
||||
public String formatRecordLabel(QTableMetaData table, QRecord record)
|
||||
{
|
||||
if(!StringUtils.hasContent(table.getRecordLabelFormat()))
|
||||
{
|
||||
@ -128,7 +130,7 @@ public class QValueFormatter
|
||||
/*******************************************************************************
|
||||
** Deal with non-happy-path cases for making a record label.
|
||||
*******************************************************************************/
|
||||
private static String formatRecordLabelExceptionalCases(QTableMetaData table, QRecord record)
|
||||
private String formatRecordLabelExceptionalCases(QTableMetaData table, QRecord record)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// if there's no record label format, then just return the primary key display value //
|
||||
@ -156,7 +158,7 @@ public class QValueFormatter
|
||||
/*******************************************************************************
|
||||
** For a list of records, set their recordLabels and display values
|
||||
*******************************************************************************/
|
||||
public static void setDisplayValuesInRecords(QTableMetaData table, List<QRecord> records)
|
||||
public void setDisplayValuesInRecords(QTableMetaData table, List<QRecord> records)
|
||||
{
|
||||
if(records == null)
|
||||
{
|
||||
@ -167,11 +169,11 @@ public class QValueFormatter
|
||||
{
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
String formattedValue = QValueFormatter.formatValue(field, record.getValue(field.getName()));
|
||||
String formattedValue = formatValue(field, record.getValue(field.getName()));
|
||||
record.setDisplayValue(field.getName(), formattedValue);
|
||||
}
|
||||
|
||||
record.setRecordLabel(QValueFormatter.formatRecordLabel(table, record));
|
||||
record.setRecordLabel(formatRecordLabel(table, record));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,6 +95,15 @@ public class CsvToQRecordAdapter
|
||||
throw (new IllegalArgumentException("Empty csv value was provided."));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// once, from a DOS csv file (that had come from Excel), we had a "" character (FEFF, Byte-order marker) at the start of a //
|
||||
// CSV, which caused our first header to not match... So, let us strip away any FEFF or FFFE's at the start of CSV strings. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(csv.length() > 1 && (csv.charAt(0) == 0xfeff || csv.charAt(0) == 0xfffe))
|
||||
{
|
||||
csv = csv.substring(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -118,7 +127,7 @@ public class CsvToQRecordAdapter
|
||||
// put values from the CSV record into a map of header -> value //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
Map<String, String> csvValues = new HashMap<>();
|
||||
for(int i = 0; i < headers.size(); i++)
|
||||
for(int i = 0; i < headers.size() && i < csvRecord.size(); i++)
|
||||
{
|
||||
csvValues.put(headers.get(i), csvRecord.get(i));
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ public class QInstanceValidationException extends QException
|
||||
{
|
||||
super(
|
||||
(reasons != null && reasons.size() > 0)
|
||||
? "Instance validation failed for the following reasons: " + StringUtils.joinWithCommasAndAnd(reasons)
|
||||
? "Instance validation failed for the following reasons:\n - " + StringUtils.join("\n - ", reasons)
|
||||
: "Validation failed, but no reasons were provided");
|
||||
|
||||
if(reasons != null && reasons.size() > 0)
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.instances;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@ -128,6 +129,11 @@ public class QInstanceEnricher
|
||||
{
|
||||
generateTableFieldSections(table);
|
||||
}
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(table.getRecordLabelFields()) && !StringUtils.hasContent(table.getRecordLabelFormat()))
|
||||
{
|
||||
table.setRecordLabelFormat(String.join(" ", Collections.nCopies(table.getRecordLabelFields().size(), "%s")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -211,7 +217,7 @@ public class QInstanceEnricher
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String nameToLabel(String name)
|
||||
static String nameToLabel(String name)
|
||||
{
|
||||
if(!StringUtils.hasContent(name))
|
||||
{
|
||||
@ -223,7 +229,21 @@ public class QInstanceEnricher
|
||||
return (name.substring(0, 1).toUpperCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).replaceAll("([A-Z])", " $1"));
|
||||
String suffix = name.substring(1)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Put a space before capital letters or numbers embedded in a name //
|
||||
// e.g., omethingElse -> omething Else; umber1 -> umber 1 //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
.replaceAll("([A-Z0-9]+)", " $1")
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// put a space between numbers and words that come after them //
|
||||
// e.g., umber1dad -> number 1 dad //
|
||||
////////////////////////////////////////////////////////////////
|
||||
.replaceAll("([0-9])([A-Za-z])", "$1 $2");
|
||||
|
||||
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + suffix);
|
||||
}
|
||||
|
||||
|
||||
@ -579,7 +599,7 @@ public class QInstanceEnricher
|
||||
{
|
||||
for(String fieldName : table.getRecordLabelFields())
|
||||
{
|
||||
if(!usedFieldNames.contains(fieldName))
|
||||
if(!usedFieldNames.contains(fieldName) && table.getFields().containsKey(fieldName))
|
||||
{
|
||||
identitySection.getFieldNames().add(fieldName);
|
||||
usedFieldNames.add(fieldName);
|
||||
|
@ -22,13 +22,21 @@
|
||||
package com.kingsrook.qqq.backend.core.instances;
|
||||
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
@ -37,6 +45,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -51,6 +61,11 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
*******************************************************************************/
|
||||
public class QInstanceValidator
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QInstanceValidator.class);
|
||||
|
||||
private boolean printWarnings = false;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
@ -88,6 +103,7 @@ public class QInstanceValidator
|
||||
validateTables(qInstance, errors);
|
||||
validateProcesses(qInstance, errors);
|
||||
validateApps(qInstance, errors);
|
||||
validatePossibleValueSources(qInstance, errors);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -167,8 +183,8 @@ public class QInstanceValidator
|
||||
//////////////////////////////////////////
|
||||
// validate field sections in the table //
|
||||
//////////////////////////////////////////
|
||||
Set<String> fieldNamesInSections = new HashSet<>();
|
||||
QFieldSection tier1Section = null;
|
||||
Set<String> fieldNamesInSections = new HashSet<>();
|
||||
QFieldSection tier1Section = null;
|
||||
if(table.getSections() != null)
|
||||
{
|
||||
for(QFieldSection section : table.getSections())
|
||||
@ -190,12 +206,140 @@ public class QInstanceValidator
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// validate the record label //
|
||||
///////////////////////////////
|
||||
if(table.getRecordLabelFields() != null)
|
||||
{
|
||||
for(String recordLabelField : table.getRecordLabelFields())
|
||||
{
|
||||
assertCondition(errors, table.getFields().containsKey(recordLabelField), "Table " + tableName + " record label field " + recordLabelField + " is not a field on this table.");
|
||||
}
|
||||
}
|
||||
|
||||
if(table.getCustomizers() != null)
|
||||
{
|
||||
for(Map.Entry<String, QCodeReference> entry : table.getCustomizers().entrySet())
|
||||
{
|
||||
validateTableCustomizer(errors, tableName, entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateTableCustomizer(List<String> errors, String tableName, String customizerName, QCodeReference codeReference)
|
||||
{
|
||||
String prefix = "Table " + tableName + ", customizer " + customizerName + ": ";
|
||||
|
||||
if(!preAssertionsForCodeReference(errors, codeReference, prefix))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// make sure (at this time) that it's a java type, then do some java checks //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
if(assertCondition(errors, codeReference.getCodeType().equals(QCodeType.JAVA), prefix + "Only JAVA customizers are supported at this time."))
|
||||
{
|
||||
///////////////////////////////////////
|
||||
// make sure the class can be loaded //
|
||||
///////////////////////////////////////
|
||||
Class<?> customizerClass = getClassForCodeReference(errors, codeReference, prefix);
|
||||
if(customizerClass != null)
|
||||
{
|
||||
//////////////////////////////////////////////////
|
||||
// make sure the customizer can be instantiated //
|
||||
//////////////////////////////////////////////////
|
||||
Object customizerInstance = getInstanceOfCodeReference(errors, prefix, customizerClass);
|
||||
|
||||
TableCustomizers tableCustomizer = TableCustomizers.forRole(customizerName);
|
||||
if(tableCustomizer == null)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - in the future, load customizers from backend-modules (e.g., FilesystemTableCustomizers) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
warn(prefix + "Unrecognized table customizer name (at least at backend-core level)");
|
||||
}
|
||||
else
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// make sure the customizer instance can be cast to the expected type //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
if(customizerInstance != null && tableCustomizer.getTableCustomizer().getExpectedType() != null)
|
||||
{
|
||||
Object castedObject = getCastedObject(errors, prefix, tableCustomizer.getTableCustomizer().getExpectedType(), customizerInstance);
|
||||
|
||||
Consumer<Object> validationFunction = tableCustomizer.getTableCustomizer().getValidationFunction();
|
||||
if(castedObject != null && validationFunction != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
validationFunction.accept(castedObject);
|
||||
}
|
||||
catch(ClassCastException e)
|
||||
{
|
||||
errors.add(prefix + "Error validating customizer type parameters: " + e.getMessage());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// mmm, calling customizers w/ random data is expected to often throw, so, this check is iffy at best... //
|
||||
// if we run into more trouble here, we might consider disabling the whole "validation function" check. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private <T> T getCastedObject(List<String> errors, String prefix, Class<T> expectedType, Object customizerInstance)
|
||||
{
|
||||
T castedObject = null;
|
||||
try
|
||||
{
|
||||
castedObject = expectedType.cast(customizerInstance);
|
||||
}
|
||||
catch(ClassCastException e)
|
||||
{
|
||||
errors.add(prefix + "CodeReference could not be casted to the expected type: " + expectedType);
|
||||
}
|
||||
return castedObject;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Object getInstanceOfCodeReference(List<String> errors, String prefix, Class<?> customizerClass)
|
||||
{
|
||||
Object customizerInstance = null;
|
||||
try
|
||||
{
|
||||
customizerInstance = customizerClass.getConstructor().newInstance();
|
||||
}
|
||||
catch(InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e)
|
||||
{
|
||||
errors.add(prefix + "Instance of CodeReference could not be created: " + e);
|
||||
}
|
||||
return customizerInstance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -225,7 +369,7 @@ public class QInstanceValidator
|
||||
*******************************************************************************/
|
||||
private void validateProcesses(QInstance qInstance, List<String> errors)
|
||||
{
|
||||
if(!CollectionUtils.nullSafeIsEmpty(qInstance.getProcesses()))
|
||||
if(CollectionUtils.nullSafeHasContents(qInstance.getProcesses()))
|
||||
{
|
||||
qInstance.getProcesses().forEach((processName, process) ->
|
||||
{
|
||||
@ -264,7 +408,7 @@ public class QInstanceValidator
|
||||
*******************************************************************************/
|
||||
private void validateApps(QInstance qInstance, List<String> errors)
|
||||
{
|
||||
if(!CollectionUtils.nullSafeIsEmpty(qInstance.getApps()))
|
||||
if(CollectionUtils.nullSafeHasContents(qInstance.getApps()))
|
||||
{
|
||||
qInstance.getApps().forEach((appName, app) ->
|
||||
{
|
||||
@ -291,6 +435,142 @@ public class QInstanceValidator
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validatePossibleValueSources(QInstance qInstance, List<String> errors)
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(qInstance.getPossibleValueSources()))
|
||||
{
|
||||
qInstance.getPossibleValueSources().forEach((pvsName, possibleValueSource) ->
|
||||
{
|
||||
assertCondition(errors, Objects.equals(pvsName, possibleValueSource.getName()), "Inconsistent naming for possibleValueSource: " + pvsName + "/" + possibleValueSource.getName() + ".");
|
||||
if(assertCondition(errors, possibleValueSource.getType() != null, "Missing type for possibleValueSource: " + pvsName))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// assert about fields that should and should not be set, based on possible value source type //
|
||||
// do additional type-specific validations as well //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
switch(possibleValueSource.getType())
|
||||
{
|
||||
case ENUM ->
|
||||
{
|
||||
assertCondition(errors, !StringUtils.hasContent(possibleValueSource.getTableName()), "enum-type possibleValueSource " + pvsName + " should not have a tableName.");
|
||||
assertCondition(errors, possibleValueSource.getCustomCodeReference() == null, "enum-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
|
||||
|
||||
assertCondition(errors, CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()), "enum-type possibleValueSource " + pvsName + " is missing enum values");
|
||||
}
|
||||
case TABLE ->
|
||||
{
|
||||
assertCondition(errors, CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "table-type possibleValueSource " + pvsName + " should not have enum values.");
|
||||
assertCondition(errors, possibleValueSource.getCustomCodeReference() == null, "table-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
|
||||
|
||||
if(assertCondition(errors, StringUtils.hasContent(possibleValueSource.getTableName()), "table-type possibleValueSource " + pvsName + " is missing a tableName."))
|
||||
{
|
||||
assertCondition(errors, qInstance.getTable(possibleValueSource.getTableName()) != null, "Unrecognized table " + possibleValueSource.getTableName() + " for possibleValueSource " + pvsName + ".");
|
||||
}
|
||||
}
|
||||
case CUSTOM ->
|
||||
{
|
||||
assertCondition(errors, CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "custom-type possibleValueSource " + pvsName + " should not have enum values.");
|
||||
assertCondition(errors, !StringUtils.hasContent(possibleValueSource.getTableName()), "custom-type possibleValueSource " + pvsName + " should not have a tableName.");
|
||||
|
||||
if(assertCondition(errors, possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + pvsName + " is missing a customCodeReference."))
|
||||
{
|
||||
assertCondition(errors, QCodeUsage.POSSIBLE_VALUE_PROVIDER.equals(possibleValueSource.getCustomCodeReference().getCodeUsage()), "customCodeReference for possibleValueSource " + pvsName + " is not a possibleValueProvider.");
|
||||
validateCustomPossibleValueSourceCode(errors, pvsName, possibleValueSource.getCustomCodeReference());
|
||||
}
|
||||
}
|
||||
default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateCustomPossibleValueSourceCode(List<String> errors, String pvsName, QCodeReference codeReference)
|
||||
{
|
||||
String prefix = "PossibleValueSource " + pvsName + " custom code reference: ";
|
||||
|
||||
if(!preAssertionsForCodeReference(errors, codeReference, prefix))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// make sure (at this time) that it's a java type, then do some java checks //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
if(assertCondition(errors, codeReference.getCodeType().equals(QCodeType.JAVA), prefix + "Only JAVA customizers are supported at this time."))
|
||||
{
|
||||
///////////////////////////////////////
|
||||
// make sure the class can be loaded //
|
||||
///////////////////////////////////////
|
||||
Class<?> customizerClass = getClassForCodeReference(errors, codeReference, prefix);
|
||||
if(customizerClass != null)
|
||||
{
|
||||
//////////////////////////////////////////////////
|
||||
// make sure the customizer can be instantiated //
|
||||
//////////////////////////////////////////////////
|
||||
Object customizerInstance = getInstanceOfCodeReference(errors, prefix, customizerClass);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// make sure the customizer instance can be cast to the expected type //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
if(customizerInstance != null)
|
||||
{
|
||||
getCastedObject(errors, prefix, QCustomPossibleValueProvider.class, customizerInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Class<?> getClassForCodeReference(List<String> errors, QCodeReference codeReference, String prefix)
|
||||
{
|
||||
Class<?> customizerClass = null;
|
||||
try
|
||||
{
|
||||
customizerClass = Class.forName(codeReference.getName());
|
||||
}
|
||||
catch(ClassNotFoundException e)
|
||||
{
|
||||
errors.add(prefix + "Class for CodeReference could not be found.");
|
||||
}
|
||||
return customizerClass;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private boolean preAssertionsForCodeReference(List<String> errors, QCodeReference codeReference, String prefix)
|
||||
{
|
||||
boolean okay = true;
|
||||
if(!assertCondition(errors, StringUtils.hasContent(codeReference.getName()), prefix + " is missing a code reference name"))
|
||||
{
|
||||
okay = false;
|
||||
}
|
||||
|
||||
if(!assertCondition(errors, codeReference.getCodeType() != null, prefix + " is missing a code type"))
|
||||
{
|
||||
okay = false;
|
||||
}
|
||||
|
||||
return (okay);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Check if an app's child list can recursively be traversed without finding a
|
||||
** duplicate, which would indicate a cycle (e.g., an error)
|
||||
@ -343,4 +623,16 @@ public class QInstanceValidator
|
||||
return (condition);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void warn(String message)
|
||||
{
|
||||
if(printWarnings)
|
||||
{
|
||||
LOG.info("Validation warning: " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,8 @@ public class QueryInput extends AbstractTableActionInput
|
||||
|
||||
private RecordPipe recordPipe;
|
||||
|
||||
private boolean shouldTranslatePossibleValues = false;
|
||||
private boolean shouldGenerateDisplayValues = false;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -158,4 +160,47 @@ public class QueryInput extends AbstractTableActionInput
|
||||
this.recordPipe = recordPipe;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shouldTranslatePossibleValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean getShouldTranslatePossibleValues()
|
||||
{
|
||||
return shouldTranslatePossibleValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for shouldTranslatePossibleValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setShouldTranslatePossibleValues(boolean shouldTranslatePossibleValues)
|
||||
{
|
||||
this.shouldTranslatePossibleValues = shouldTranslatePossibleValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shouldGenerateDisplayValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean getShouldGenerateDisplayValues()
|
||||
{
|
||||
return shouldGenerateDisplayValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for shouldGenerateDisplayValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setShouldGenerateDisplayValues(boolean shouldGenerateDisplayValues)
|
||||
{
|
||||
this.shouldGenerateDisplayValues = shouldGenerateDisplayValues;
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,11 @@ public @interface QField
|
||||
*******************************************************************************/
|
||||
String displayFormat() default "";
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String possibleValueSourceName() default "";
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// new attributes here likely need implementation in QFieldMetaData.constructFromGetter //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@ -328,6 +329,16 @@ public class QRecord implements Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public LocalTime getValueLocalTime(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsLocalTime(values.get(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -54,10 +54,10 @@ public class QInstance
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Important to use LinkedHashmap here, to preserve the order in which entries are added. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private Map<String, QTableMetaData> tables = new LinkedHashMap<>();
|
||||
private Map<String, QPossibleValueSource<?>> possibleValueSources = new LinkedHashMap<>();
|
||||
private Map<String, QProcessMetaData> processes = new LinkedHashMap<>();
|
||||
private Map<String, QAppMetaData> apps = new LinkedHashMap<>();
|
||||
private Map<String, QTableMetaData> tables = new LinkedHashMap<>();
|
||||
private Map<String, QPossibleValueSource> possibleValueSources = new LinkedHashMap<>();
|
||||
private Map<String, QProcessMetaData> processes = new LinkedHashMap<>();
|
||||
private Map<String, QAppMetaData> apps = new LinkedHashMap<>();
|
||||
|
||||
// todo - lock down the object (no more changes allowed) after it's been validated?
|
||||
|
||||
@ -190,7 +190,7 @@ public class QInstance
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addPossibleValueSource(QPossibleValueSource<?> possibleValueSource)
|
||||
public void addPossibleValueSource(QPossibleValueSource possibleValueSource)
|
||||
{
|
||||
this.addPossibleValueSource(possibleValueSource.getName(), possibleValueSource);
|
||||
}
|
||||
@ -353,7 +353,7 @@ public class QInstance
|
||||
** Getter for possibleValueSources
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QPossibleValueSource<?>> getPossibleValueSources()
|
||||
public Map<String, QPossibleValueSource> getPossibleValueSources()
|
||||
{
|
||||
return possibleValueSources;
|
||||
}
|
||||
@ -364,7 +364,7 @@ public class QInstance
|
||||
** Setter for possibleValueSources
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setPossibleValueSources(Map<String, QPossibleValueSource<?>> possibleValueSources)
|
||||
public void setPossibleValueSources(Map<String, QPossibleValueSource> possibleValueSources)
|
||||
{
|
||||
this.possibleValueSources = possibleValueSources;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.code;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -70,6 +71,10 @@ public class QCodeReference
|
||||
{
|
||||
this.codeUsage = QCodeUsage.BACKEND_STEP;
|
||||
}
|
||||
else if(QCustomPossibleValueProvider.class.isAssignableFrom(javaClass))
|
||||
{
|
||||
this.codeUsage = QCodeUsage.POSSIBLE_VALUE_PROVIDER;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new IllegalStateException("Unable to infer code usage type for class: " + javaClass.getName()));
|
||||
|
@ -29,5 +29,6 @@ package com.kingsrook.qqq.backend.core.model.metadata.code;
|
||||
public enum QCodeUsage
|
||||
{
|
||||
BACKEND_STEP, // a backend-step in a process
|
||||
CUSTOMIZER // a function to customize part of a QQQ table's behavior
|
||||
CUSTOMIZER, // a function to customize part of a QQQ table's behavior
|
||||
POSSIBLE_VALUE_PROVIDER // code that drives a custom possibleValueSource
|
||||
}
|
||||
|
@ -133,6 +133,11 @@ public class QFieldMetaData
|
||||
{
|
||||
setDisplayFormat(fieldAnnotation.displayFormat());
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(fieldAnnotation.possibleValueSourceName()))
|
||||
{
|
||||
setPossibleValueSourceName(fieldAnnotation.possibleValueSourceName());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(QException qe)
|
||||
@ -406,6 +411,7 @@ public class QFieldMetaData
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for displayFormat
|
||||
**
|
||||
@ -427,6 +433,7 @@ public class QFieldMetaData
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for displayFormat
|
||||
**
|
||||
|
@ -36,11 +36,13 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public class QFrontendFieldMetaData
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
private String name;
|
||||
private String label;
|
||||
private QFieldType type;
|
||||
private boolean isRequired;
|
||||
private boolean isEditable;
|
||||
private boolean isRequired;
|
||||
private boolean isEditable;
|
||||
private String possibleValueSourceName;
|
||||
private String displayFormat;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// do not add setters. take values from the source-object in the constructor!! //
|
||||
@ -58,6 +60,8 @@ public class QFrontendFieldMetaData
|
||||
this.type = fieldMetaData.getType();
|
||||
this.isRequired = fieldMetaData.getIsRequired();
|
||||
this.isEditable = fieldMetaData.getIsEditable();
|
||||
this.possibleValueSourceName = fieldMetaData.getPossibleValueSourceName();
|
||||
this.displayFormat = fieldMetaData.getDisplayFormat();
|
||||
}
|
||||
|
||||
|
||||
@ -115,4 +119,26 @@ public class QFrontendFieldMetaData
|
||||
return isEditable;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for displayFormat
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getDisplayFormat()
|
||||
{
|
||||
return displayFormat;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for possibleValueSourceName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getPossibleValueSourceName()
|
||||
{
|
||||
return possibleValueSourceName;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define some standard ways to format the value portion of a PossibleValueSource.
|
||||
**
|
||||
** Can be passed to short-cut {set,with}ValueFormatAndFields methods in QPossibleValueSource
|
||||
** class, or the format & field properties can be extracted and passed to regular field-level setters.
|
||||
*******************************************************************************/
|
||||
public enum PVSValueFormatAndFields
|
||||
{
|
||||
LABEL_ONLY("%s", "label"),
|
||||
LABEL_PARENS_ID("%s (%s)", "label", "id"),
|
||||
ID_COLON_LABEL("%s: %s", "id", "label");
|
||||
|
||||
|
||||
private final String format;
|
||||
private final List<String> fields;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
PVSValueFormatAndFields(String format, String... fields)
|
||||
{
|
||||
this.format = format;
|
||||
this.fields = List.of(fields);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for format
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getFormat()
|
||||
{
|
||||
return format;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fields
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<String> getFields()
|
||||
{
|
||||
return fields;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.possiblevalues;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface to be implemented by enums which can be used as a PossibleValueSource.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface PossibleValueEnum<T>
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
T getPossibleValueId();
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String getPossibleValueLabel();
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.possiblevalues;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** An actual possible value - an id and label.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QPossibleValue<T>
|
||||
{
|
||||
private final T id;
|
||||
private final String label;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public QPossibleValue(String value)
|
||||
{
|
||||
this.id = (T) value;
|
||||
this.label = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValue(T id, String label)
|
||||
{
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
**
|
||||
*******************************************************************************/
|
||||
public T getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for label
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getLabel()
|
||||
{
|
||||
return label;
|
||||
}
|
||||
}
|
@ -24,19 +24,42 @@ package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meta-data to represent a single field in a table.
|
||||
** Meta-data to represent a "Possible value" - e.g., a translation of a foreign
|
||||
** key and/or a limited set of "possible values" for a field (e.g., from a foreign
|
||||
** table or an enum).
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QPossibleValueSource<T>
|
||||
public class QPossibleValueSource
|
||||
{
|
||||
private String name;
|
||||
private String name;
|
||||
private QPossibleValueSourceType type;
|
||||
|
||||
// should these be in sub-types??
|
||||
private List<T> enumValues;
|
||||
private String valueFormat = PVSValueFormatAndFields.LABEL_ONLY.getFormat();
|
||||
private List<String> valueFields = PVSValueFormatAndFields.LABEL_ONLY.getFields();
|
||||
private String valueFormatIfNotFound = null;
|
||||
private List<String> valueFieldsIfNotFound = null;
|
||||
|
||||
// todo - optimization hints, such as "table is static, fully cache" or "table is small, so we can pull the whole thing into memory?"
|
||||
|
||||
//////////////////////
|
||||
// for type = TABLE //
|
||||
//////////////////////
|
||||
private String tableName;
|
||||
// todo - override labelFormat & labelFields?
|
||||
|
||||
/////////////////////
|
||||
// for type = ENUM //
|
||||
/////////////////////
|
||||
private List<QPossibleValue<?>> enumValues;
|
||||
|
||||
///////////////////////
|
||||
// for type = CUSTOM //
|
||||
///////////////////////
|
||||
private QCodeReference customCodeReference;
|
||||
|
||||
|
||||
|
||||
@ -72,7 +95,7 @@ public class QPossibleValueSource<T>
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource<T> withName(String name)
|
||||
public QPossibleValueSource withName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
return (this);
|
||||
@ -103,7 +126,7 @@ public class QPossibleValueSource<T>
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource<T> withType(QPossibleValueSourceType type)
|
||||
public QPossibleValueSource withType(QPossibleValueSourceType type)
|
||||
{
|
||||
this.type = type;
|
||||
return (this);
|
||||
@ -111,11 +134,181 @@ public class QPossibleValueSource<T>
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for valueFormat
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getValueFormat()
|
||||
{
|
||||
return valueFormat;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for valueFormat
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setValueFormat(String valueFormat)
|
||||
{
|
||||
this.valueFormat = valueFormat;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for valueFormat
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withValueFormat(String valueFormat)
|
||||
{
|
||||
this.valueFormat = valueFormat;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for valueFields
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<String> getValueFields()
|
||||
{
|
||||
return valueFields;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for valueFields
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setValueFields(List<String> valueFields)
|
||||
{
|
||||
this.valueFields = valueFields;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for valueFields
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withValueFields(List<String> valueFields)
|
||||
{
|
||||
this.valueFields = valueFields;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for valueFormatIfNotFound
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getValueFormatIfNotFound()
|
||||
{
|
||||
return valueFormatIfNotFound;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for valueFormatIfNotFound
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setValueFormatIfNotFound(String valueFormatIfNotFound)
|
||||
{
|
||||
this.valueFormatIfNotFound = valueFormatIfNotFound;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for valueFormatIfNotFound
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withValueFormatIfNotFound(String valueFormatIfNotFound)
|
||||
{
|
||||
this.valueFormatIfNotFound = valueFormatIfNotFound;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for valueFieldsIfNotFound
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<String> getValueFieldsIfNotFound()
|
||||
{
|
||||
return valueFieldsIfNotFound;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for valueFieldsIfNotFound
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setValueFieldsIfNotFound(List<String> valueFieldsIfNotFound)
|
||||
{
|
||||
this.valueFieldsIfNotFound = valueFieldsIfNotFound;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for valueFieldsIfNotFound
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withValueFieldsIfNotFound(List<String> valueFieldsIfNotFound)
|
||||
{
|
||||
this.valueFieldsIfNotFound = valueFieldsIfNotFound;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for enumValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<T> getEnumValues()
|
||||
public List<QPossibleValue<?>> getEnumValues()
|
||||
{
|
||||
return enumValues;
|
||||
}
|
||||
@ -126,7 +319,7 @@ public class QPossibleValueSource<T>
|
||||
** Setter for enumValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setEnumValues(List<T> enumValues)
|
||||
public void setEnumValues(List<QPossibleValue<?>> enumValues)
|
||||
{
|
||||
this.enumValues = enumValues;
|
||||
}
|
||||
@ -137,7 +330,7 @@ public class QPossibleValueSource<T>
|
||||
** Fluent setter for enumValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource<T> withEnumValues(List<T> enumValues)
|
||||
public QPossibleValueSource withEnumValues(List<QPossibleValue<?>> enumValues)
|
||||
{
|
||||
this.enumValues = enumValues;
|
||||
return this;
|
||||
@ -146,16 +339,89 @@ public class QPossibleValueSource<T>
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent adder for enumValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource<T> addEnumValue(T enumValue)
|
||||
public void addEnumValue(QPossibleValue<?> possibleValue)
|
||||
{
|
||||
if(this.enumValues == null)
|
||||
{
|
||||
this.enumValues = new ArrayList<>();
|
||||
}
|
||||
this.enumValues.add(enumValue);
|
||||
return this;
|
||||
this.enumValues.add(possibleValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** This is the easiest way to add the values from an enum to a PossibleValueSource.
|
||||
** Make sure the enum implements PossibleValueEnum - then call as:
|
||||
** myPossibleValueSource.withValuesFromEnum(MyEnum.values()));
|
||||
**
|
||||
*******************************************************************************/
|
||||
public <T extends PossibleValueEnum<?>> QPossibleValueSource withValuesFromEnum(T[] values)
|
||||
{
|
||||
for(T t : values)
|
||||
{
|
||||
addEnumValue(new QPossibleValue<>(t.getPossibleValueId(), t.getPossibleValueLabel()));
|
||||
}
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for customCodeReference
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QCodeReference getCustomCodeReference()
|
||||
{
|
||||
return customCodeReference;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for customCodeReference
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setCustomCodeReference(QCodeReference customCodeReference)
|
||||
{
|
||||
this.customCodeReference = customCodeReference;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for customCodeReference
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withCustomCodeReference(QCodeReference customCodeReference)
|
||||
{
|
||||
this.customCodeReference = customCodeReference;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setValueFormatAndFields(PVSValueFormatAndFields valueFormatAndFields)
|
||||
{
|
||||
this.valueFormat = valueFormatAndFields.getFormat();
|
||||
this.valueFields = valueFormatAndFields.getFields();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withValueFormatAndFields(PVSValueFormatAndFields valueFormatAndFields)
|
||||
{
|
||||
setValueFormatAndFields(valueFormatAndFields);
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,11 +24,13 @@ package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
|
||||
@ -84,6 +86,17 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return ("QTableMetaData[" + name + "]");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -408,12 +421,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
|
||||
}
|
||||
|
||||
QCodeReference function = customizers.get(customizerName);
|
||||
if(function == null)
|
||||
{
|
||||
throw (new IllegalArgumentException("Customizer [" + customizerName + "] was not found in table [" + name + "]."));
|
||||
}
|
||||
|
||||
return (Optional.of(function));
|
||||
return (Optional.ofNullable(function));
|
||||
}
|
||||
|
||||
|
||||
@ -455,6 +463,16 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withCustomizer(TableCustomizer tableCustomizer, QCodeReference customizer)
|
||||
{
|
||||
return (withCustomizer(tableCustomizer.getRole(), customizer));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -592,6 +610,18 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for recordLabelFields
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withRecordLabelFields(String... recordLabelFields)
|
||||
{
|
||||
this.recordLabelFields = Arrays.asList(recordLabelFields);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sections
|
||||
**
|
||||
|
@ -167,6 +167,11 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
return (false);
|
||||
}
|
||||
|
||||
if(session.getIdReference() == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
StateProviderInterface spi = getStateProvider();
|
||||
Auth0StateKey key = new Auth0StateKey(session.getIdReference());
|
||||
Optional<Instant> lastTimeCheckedOptional = spi.get(Instant.class, key);
|
||||
|
@ -72,6 +72,7 @@ public class QBackendModuleDispatcher
|
||||
// todo - let modules somehow "export" their types here?
|
||||
// e.g., backend-core shouldn't need to "know" about the modules.
|
||||
"com.kingsrook.qqq.backend.core.modules.backend.implementations.mock.MockBackendModule",
|
||||
"com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule",
|
||||
"com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule",
|
||||
"com.kingsrook.qqq.backend.module.filesystem.local.FilesystemBackendModule",
|
||||
"com.kingsrook.qqq.backend.module.filesystem.s3.S3BackendModule"
|
||||
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.modules.backend.implementations.memory;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.UpdateInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A simple (probably only valid for testing?) implementation of the QModuleInterface,
|
||||
** that just stores its records in-memory.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MemoryBackendModule implements QBackendModuleInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Method where a backend module must be able to provide its type (name).
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getBackendType()
|
||||
{
|
||||
return ("memory");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Method to identify the class used for backend meta data for this module.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Class<? extends QBackendMetaData> getBackendMetaDataClass()
|
||||
{
|
||||
return (QBackendMetaData.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public CountInterface getCountInterface()
|
||||
{
|
||||
return new MemoryCountAction();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QueryInterface getQueryInterface()
|
||||
{
|
||||
return new MemoryQueryAction();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public InsertInterface getInsertInterface()
|
||||
{
|
||||
return (new MemoryInsertAction());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public UpdateInterface getUpdateInterface()
|
||||
{
|
||||
return (new MemoryUpdateAction());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public DeleteInterface getDeleteInterface()
|
||||
{
|
||||
return (new MemoryDeleteAction());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.modules.backend.implementations.memory;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** In-memory version of count action.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MemoryCountAction implements CountInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public CountOutput execute(CountInput countInput) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
CountOutput countOutput = new CountOutput();
|
||||
countOutput.setCount(MemoryRecordStore.getInstance().count(countInput));
|
||||
return (countOutput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new QException("Error executing count", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.modules.backend.implementations.memory;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** In-memory version of delete action.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MemoryDeleteAction implements DeleteInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public DeleteOutput execute(DeleteInput deleteInput) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
DeleteOutput deleteOutput = new DeleteOutput();
|
||||
deleteOutput.setDeletedRecordCount(MemoryRecordStore.getInstance().delete(deleteInput));
|
||||
return (deleteOutput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new QException("Error executing delete: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.modules.backend.implementations.memory;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** In-memory version of insert action.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MemoryInsertAction implements InsertInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public InsertOutput execute(InsertInput insertInput) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
InsertOutput insertOutput = new InsertOutput();
|
||||
insertOutput.setRecords(MemoryRecordStore.getInstance().insert(insertInput, true));
|
||||
return (insertOutput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new QException("Error executing insert: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.modules.backend.implementations.memory;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** In-memory version of query action.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MemoryQueryAction implements QueryInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QueryOutput execute(QueryInput queryInput) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
QueryOutput queryOutput = new QueryOutput(queryInput);
|
||||
queryOutput.addRecords(MemoryRecordStore.getInstance().query(queryInput));
|
||||
return (queryOutput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new QException("Error executing query", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,360 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.modules.backend.implementations.memory;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
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.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Storage provider for the MemoryBackendModule
|
||||
*******************************************************************************/
|
||||
public class MemoryRecordStore
|
||||
{
|
||||
private static MemoryRecordStore instance;
|
||||
|
||||
private Map<String, Map<Serializable, QRecord>> data;
|
||||
private Map<String, Integer> nextSerials;
|
||||
|
||||
private static boolean collectStatistics = false;
|
||||
|
||||
private static final Map<String, Integer> statistics = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
public static final String STAT_QUERIES_RAN = "queriesRan";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** private singleton constructor
|
||||
*******************************************************************************/
|
||||
private MemoryRecordStore()
|
||||
{
|
||||
data = new HashMap<>();
|
||||
nextSerials = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Forget all data in the memory store...
|
||||
*******************************************************************************/
|
||||
public void reset()
|
||||
{
|
||||
data.clear();
|
||||
nextSerials.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** singleton accessor
|
||||
*******************************************************************************/
|
||||
public static MemoryRecordStore getInstance()
|
||||
{
|
||||
if(instance == null)
|
||||
{
|
||||
instance = new MemoryRecordStore();
|
||||
}
|
||||
return (instance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Map<Serializable, QRecord> getTableData(QTableMetaData table)
|
||||
{
|
||||
if(!data.containsKey(table.getName()))
|
||||
{
|
||||
data.put(table.getName(), new HashMap<>());
|
||||
}
|
||||
return (data.get(table.getName()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<QRecord> query(QueryInput input)
|
||||
{
|
||||
incrementStatistic(STAT_QUERIES_RAN);
|
||||
|
||||
Map<Serializable, QRecord> tableData = getTableData(input.getTable());
|
||||
List<QRecord> records = new ArrayList<>();
|
||||
|
||||
for(QRecord qRecord : tableData.values())
|
||||
{
|
||||
boolean recordMatches = true;
|
||||
if(input.getFilter() != null && input.getFilter().getCriteria() != null)
|
||||
{
|
||||
for(QFilterCriteria criterion : input.getFilter().getCriteria())
|
||||
{
|
||||
String fieldName = criterion.getFieldName();
|
||||
Serializable value = qRecord.getValue(fieldName);
|
||||
switch(criterion.getOperator())
|
||||
{
|
||||
case EQUALS:
|
||||
{
|
||||
if(!value.equals(criterion.getValues().get(0)))
|
||||
{
|
||||
recordMatches = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IN:
|
||||
{
|
||||
if(!criterion.getValues().contains(value))
|
||||
{
|
||||
recordMatches = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new NotImplementedException("Operator [" + criterion.getOperator() + "] is not yet implemented in the Memory backend.");
|
||||
}
|
||||
}
|
||||
if(!recordMatches)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(recordMatches)
|
||||
{
|
||||
records.add(qRecord);
|
||||
}
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer count(CountInput input)
|
||||
{
|
||||
QueryInput queryInput = new QueryInput(input.getInstance());
|
||||
queryInput.setSession(input.getSession());
|
||||
queryInput.setTableName(input.getTableName());
|
||||
queryInput.setFilter(input.getFilter());
|
||||
List<QRecord> queryResult = query(queryInput);
|
||||
|
||||
return (queryResult.size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<QRecord> insert(InsertInput input, boolean returnInsertedRecords)
|
||||
{
|
||||
if(input.getRecords() == null)
|
||||
{
|
||||
return (new ArrayList<>());
|
||||
}
|
||||
|
||||
QTableMetaData table = input.getTable();
|
||||
Map<Serializable, QRecord> tableData = getTableData(table);
|
||||
|
||||
////////////////////////////////////////
|
||||
// grab the next unique serial to use //
|
||||
////////////////////////////////////////
|
||||
Integer nextSerial = nextSerials.get(table.getName());
|
||||
if(nextSerial == null)
|
||||
{
|
||||
nextSerial = 1;
|
||||
}
|
||||
|
||||
while(tableData.containsKey(nextSerial))
|
||||
{
|
||||
nextSerial++;
|
||||
}
|
||||
|
||||
List<QRecord> outputRecords = new ArrayList<>();
|
||||
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||
for(QRecord record : input.getRecords())
|
||||
{
|
||||
/////////////////////////////////////////////////
|
||||
// set the next serial in the record if needed //
|
||||
/////////////////////////////////////////////////
|
||||
if(record.getValue(primaryKeyField.getName()) == null && primaryKeyField.getType().equals(QFieldType.INTEGER))
|
||||
{
|
||||
record.setValue(primaryKeyField.getName(), nextSerial++);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// make sure that if the user supplied a serial, greater than the one we had, that we skip ahead //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(primaryKeyField.getType().equals(QFieldType.INTEGER) && record.getValueInteger(primaryKeyField.getName()) > nextSerial)
|
||||
{
|
||||
nextSerial = record.getValueInteger(primaryKeyField.getName()) + 1;
|
||||
}
|
||||
|
||||
tableData.put(record.getValue(primaryKeyField.getName()), record);
|
||||
if(returnInsertedRecords)
|
||||
{
|
||||
outputRecords.add(record);
|
||||
}
|
||||
}
|
||||
|
||||
nextSerials.put(table.getName(), nextSerial);
|
||||
|
||||
return (outputRecords);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<QRecord> update(UpdateInput input, boolean returnUpdatedRecords)
|
||||
{
|
||||
if(input.getRecords() == null)
|
||||
{
|
||||
return (new ArrayList<>());
|
||||
}
|
||||
|
||||
QTableMetaData table = input.getTable();
|
||||
Map<Serializable, QRecord> tableData = getTableData(table);
|
||||
|
||||
List<QRecord> outputRecords = new ArrayList<>();
|
||||
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||
for(QRecord record : input.getRecords())
|
||||
{
|
||||
Serializable primaryKeyValue = record.getValue(primaryKeyField.getName());
|
||||
if(tableData.containsKey(primaryKeyValue))
|
||||
{
|
||||
QRecord recordToUpdate = tableData.get(primaryKeyValue);
|
||||
for(Map.Entry<String, Serializable> valueEntry : record.getValues().entrySet())
|
||||
{
|
||||
recordToUpdate.setValue(valueEntry.getKey(), valueEntry.getValue());
|
||||
}
|
||||
|
||||
if(returnUpdatedRecords)
|
||||
{
|
||||
outputRecords.add(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (outputRecords);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public int delete(DeleteInput input)
|
||||
{
|
||||
if(input.getPrimaryKeys() == null)
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
|
||||
QTableMetaData table = input.getTable();
|
||||
Map<Serializable, QRecord> tableData = getTableData(table);
|
||||
int rowsDeleted = 0;
|
||||
for(Serializable primaryKeyValue : input.getPrimaryKeys())
|
||||
{
|
||||
if(tableData.containsKey(primaryKeyValue))
|
||||
{
|
||||
tableData.remove(primaryKeyValue);
|
||||
rowsDeleted++;
|
||||
}
|
||||
}
|
||||
|
||||
return (rowsDeleted);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for collectStatistics
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void setCollectStatistics(boolean collectStatistics)
|
||||
{
|
||||
MemoryRecordStore.collectStatistics = collectStatistics;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Increment a statistic
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void incrementStatistic(String statName)
|
||||
{
|
||||
if(collectStatistics)
|
||||
{
|
||||
statistics.putIfAbsent(statName, 0);
|
||||
statistics.put(statName, statistics.get(statName) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** clear the map of statistics
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void resetStatistics()
|
||||
{
|
||||
statistics.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for statistics
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Map<String, Integer> getStatistics()
|
||||
{
|
||||
return statistics;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.modules.backend.implementations.memory;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.UpdateInterface;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** In-memory version of update action.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MemoryUpdateAction implements UpdateInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public UpdateOutput execute(UpdateInput updateInput) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateOutput updateOutput = new UpdateOutput();
|
||||
updateOutput.setRecords(MemoryRecordStore.getInstance().update(updateInput, true));
|
||||
return (updateOutput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new QException("Error executing update: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -82,6 +82,8 @@ public class BasicETLLoadAsUpdateFunction implements BackendStep
|
||||
for(List<QRecord> page : CollectionUtils.getPages(inputRecords, pageSize))
|
||||
{
|
||||
LOG.info("Updating a page of [" + page.size() + "] records. Progress: " + recordsUpdated + " loaded out of " + inputRecords.size() + " total");
|
||||
runBackendStepInput.getAsyncJobCallback().updateStatus("Updating records", recordsUpdated, inputRecords.size());
|
||||
|
||||
UpdateInput updateInput = new UpdateInput(runBackendStepInput.getInstance());
|
||||
updateInput.setSession(runBackendStepInput.getSession());
|
||||
updateInput.setTableName(table);
|
||||
|
@ -86,6 +86,8 @@ public class BasicETLLoadFunction implements BackendStep
|
||||
for(List<QRecord> page : CollectionUtils.getPages(inputRecords, pageSize))
|
||||
{
|
||||
LOG.info("Inserting a page of [" + page.size() + "] records. Progress: " + recordsInserted + " loaded out of " + inputRecords.size() + " total");
|
||||
runBackendStepInput.getAsyncJobCallback().updateStatus("Inserting records", recordsInserted, inputRecords.size());
|
||||
|
||||
InsertInput insertInput = new InsertInput(runBackendStepInput.getInstance());
|
||||
insertInput.setSession(runBackendStepInput.getSession());
|
||||
insertInput.setTableName(table);
|
||||
|
@ -44,6 +44,7 @@ public class ValueUtils
|
||||
{
|
||||
private static final DateTimeFormatter dateTimeFormatter_yyyyMMddWithDashes = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter dateTimeFormatter_MdyyyyWithSlashes = DateTimeFormatter.ofPattern("M/d/yyyy");
|
||||
private static final DateTimeFormatter dateTimeFormatter_yyyyMMdd = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
|
||||
|
||||
@ -262,7 +263,7 @@ public class ValueUtils
|
||||
private static LocalDate tryLocalDateParsers(String s)
|
||||
{
|
||||
DateTimeParseException lastException = null;
|
||||
for(DateTimeFormatter dateTimeFormatter : List.of(dateTimeFormatter_yyyyMMddWithDashes, dateTimeFormatter_MdyyyyWithSlashes))
|
||||
for(DateTimeFormatter dateTimeFormatter : List.of(dateTimeFormatter_yyyyMMddWithDashes, dateTimeFormatter_MdyyyyWithSlashes, dateTimeFormatter_yyyyMMdd))
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -422,7 +423,7 @@ public class ValueUtils
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Object getValueAsLocalTime(Serializable value)
|
||||
public static LocalTime getValueAsLocalTime(Serializable value)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -22,6 +22,8 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
@ -47,18 +49,79 @@ class QueryActionTest
|
||||
@Test
|
||||
public void test() throws QException
|
||||
{
|
||||
QueryInput request = new QueryInput(TestUtils.defineInstance());
|
||||
request.setSession(TestUtils.getMockSession());
|
||||
request.setTableName("person");
|
||||
QueryOutput result = new QueryAction().execute(request);
|
||||
assertNotNull(result);
|
||||
QueryInput queryInput = new QueryInput(TestUtils.defineInstance());
|
||||
queryInput.setSession(TestUtils.getMockSession());
|
||||
queryInput.setTableName("person");
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
assertNotNull(queryOutput);
|
||||
|
||||
assertThat(result.getRecords()).isNotEmpty();
|
||||
for(QRecord record : result.getRecords())
|
||||
assertThat(queryOutput.getRecords()).isNotEmpty();
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
assertThat(record.getValues()).isNotEmpty();
|
||||
assertThat(record.getDisplayValues()).isNotEmpty();
|
||||
assertThat(record.getErrors()).isEmpty();
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// this SHOULD be empty, based on the default for the should //
|
||||
///////////////////////////////////////////////////////////////
|
||||
assertThat(record.getDisplayValues()).isEmpty();
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// now flip that field and re-run //
|
||||
////////////////////////////////////
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
assertThat(queryOutput.getRecords()).isNotEmpty();
|
||||
queryOutput = new QueryAction().execute(queryInput);
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
assertThat(record.getDisplayValues()).isNotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Test running with a recordPipe - using the shape table, which uses the memory
|
||||
** backend, which is known to do an addAll to the query output.
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testRecordPipeShapeTable() throws QException
|
||||
{
|
||||
TestUtils.insertDefaultShapes(TestUtils.defineInstance());
|
||||
|
||||
RecordPipe pipe = new RecordPipe();
|
||||
QueryInput queryInput = new QueryInput(TestUtils.defineInstance());
|
||||
queryInput.setSession(TestUtils.getMockSession());
|
||||
queryInput.setTableName(TestUtils.TABLE_NAME_SHAPE);
|
||||
queryInput.setRecordPipe(pipe);
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
assertNotNull(queryOutput);
|
||||
|
||||
List<QRecord> records = pipe.consumeAvailableRecords();
|
||||
assertThat(records).isNotEmpty();
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Test running with a recordPipe - using the person table, which uses the mock
|
||||
** backend, which is known to do a single-add (not addAll) to the query output.
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testRecordPipePersonTable() throws QException
|
||||
{
|
||||
RecordPipe pipe = new RecordPipe();
|
||||
QueryInput queryInput = new QueryInput(TestUtils.defineInstance());
|
||||
queryInput.setSession(TestUtils.getMockSession());
|
||||
queryInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||
queryInput.setRecordPipe(pipe);
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
assertNotNull(queryOutput);
|
||||
|
||||
List<QRecord> records = pipe.consumeAvailableRecords();
|
||||
assertThat(records).isNotEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,335 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.actions.values;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
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.possiblevalues.PVSValueFormatAndFields;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for QPossibleValueTranslator
|
||||
*******************************************************************************/
|
||||
public class QPossibleValueTranslatorTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
void beforeEach()
|
||||
{
|
||||
MemoryRecordStore.getInstance().reset();
|
||||
MemoryRecordStore.resetStatistics();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPossibleValueEnum()
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
QPossibleValueTranslator possibleValueTranslator = new QPossibleValueTranslator(qInstance, new QSession());
|
||||
QFieldMetaData stateField = qInstance.getTable("person").getField("homeStateId");
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(stateField.getPossibleValueSourceName());
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// assert the default formatting for a not-found value is a null string //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
assertNull(possibleValueTranslator.translatePossibleValue(stateField, null));
|
||||
assertNull(possibleValueTranslator.translatePossibleValue(stateField, -1));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// let the not-found value be a simple string (no formatted values) //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
possibleValueSource.setValueFormatIfNotFound("?");
|
||||
assertEquals("?", possibleValueTranslator.translatePossibleValue(stateField, null));
|
||||
assertEquals("?", possibleValueTranslator.translatePossibleValue(stateField, -1));
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// let the not-found value be a string w/ formatted values //
|
||||
/////////////////////////////////////////////////////////////
|
||||
possibleValueSource.setValueFormatIfNotFound("? (%s)");
|
||||
possibleValueSource.setValueFieldsIfNotFound(List.of("id"));
|
||||
assertEquals("? ()", possibleValueTranslator.translatePossibleValue(stateField, null));
|
||||
assertEquals("? (-1)", possibleValueTranslator.translatePossibleValue(stateField, -1));
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// assert the default formatting is just the label //
|
||||
/////////////////////////////////////////////////////
|
||||
assertEquals("MO", possibleValueTranslator.translatePossibleValue(stateField, 2));
|
||||
assertEquals("IL", possibleValueTranslator.translatePossibleValue(stateField, 1));
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// assert the LABEL_ONLY format (when called out specifically) //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
possibleValueSource.setValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY);
|
||||
assertEquals("IL", possibleValueTranslator.translatePossibleValue(stateField, 1));
|
||||
|
||||
///////////////////////////////////////
|
||||
// assert the LABEL_PARAMS_ID format //
|
||||
///////////////////////////////////////
|
||||
possibleValueSource.setValueFormatAndFields(PVSValueFormatAndFields.LABEL_PARENS_ID);
|
||||
assertEquals("IL (1)", possibleValueTranslator.translatePossibleValue(stateField, 1));
|
||||
|
||||
//////////////////////////////////////
|
||||
// assert the ID_COLON_LABEL format //
|
||||
//////////////////////////////////////
|
||||
possibleValueSource.setValueFormat(PVSValueFormatAndFields.ID_COLON_LABEL.getFormat());
|
||||
possibleValueSource.setValueFields(PVSValueFormatAndFields.ID_COLON_LABEL.getFields());
|
||||
assertEquals("1: IL", possibleValueTranslator.translatePossibleValue(stateField, 1));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPossibleValueTable() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
QPossibleValueTranslator possibleValueTranslator = new QPossibleValueTranslator(qInstance, new QSession());
|
||||
QTableMetaData shapeTable = qInstance.getTable(TestUtils.TABLE_NAME_SHAPE);
|
||||
QFieldMetaData shapeField = qInstance.getTable(TestUtils.TABLE_NAME_PERSON).getField("favoriteShapeId");
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(shapeField.getPossibleValueSourceName());
|
||||
|
||||
TestUtils.insertDefaultShapes(qInstance);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// assert the default formatting for a not-found value is a null string //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
assertNull(possibleValueTranslator.translatePossibleValue(shapeField, null));
|
||||
assertNull(possibleValueTranslator.translatePossibleValue(shapeField, -1));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// let the not-found value be a simple string (no formatted values) //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
possibleValueSource.setValueFormatIfNotFound("?");
|
||||
assertEquals("?", possibleValueTranslator.translatePossibleValue(shapeField, null));
|
||||
assertEquals("?", possibleValueTranslator.translatePossibleValue(shapeField, -1));
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// assert the default formatting is just the label //
|
||||
/////////////////////////////////////////////////////
|
||||
assertEquals("Square", possibleValueTranslator.translatePossibleValue(shapeField, 2));
|
||||
assertEquals("Triangle", possibleValueTranslator.translatePossibleValue(shapeField, 1));
|
||||
|
||||
///////////////////////////////////////
|
||||
// assert the LABEL_PARAMS_ID format //
|
||||
///////////////////////////////////////
|
||||
possibleValueSource.setValueFormatAndFields(PVSValueFormatAndFields.LABEL_PARENS_ID);
|
||||
assertEquals("Circle (3)", possibleValueTranslator.translatePossibleValue(shapeField, 3));
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// assert that we don't re-run queries for cached values //
|
||||
///////////////////////////////////////////////////////////
|
||||
possibleValueTranslator = new QPossibleValueTranslator(qInstance, new QSession());
|
||||
MemoryRecordStore.setCollectStatistics(true);
|
||||
possibleValueTranslator.translatePossibleValue(shapeField, 1);
|
||||
possibleValueTranslator.translatePossibleValue(shapeField, 2);
|
||||
assertEquals(2, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN), "Should have ran 2 queries so far");
|
||||
possibleValueTranslator.translatePossibleValue(shapeField, 2);
|
||||
possibleValueTranslator.translatePossibleValue(shapeField, 3);
|
||||
possibleValueTranslator.translatePossibleValue(shapeField, 3);
|
||||
assertEquals(3, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN), "Should have ran 3 queries in total");
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// assert that if we prime the cache, we can do just 1 query //
|
||||
///////////////////////////////////////////////////////////////
|
||||
possibleValueTranslator = new QPossibleValueTranslator(qInstance, new QSession());
|
||||
List<QRecord> personRecords = List.of(
|
||||
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 1),
|
||||
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 1),
|
||||
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 2),
|
||||
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 2),
|
||||
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 3)
|
||||
);
|
||||
QTableMetaData personTable = qInstance.getTable(TestUtils.TABLE_NAME_PERSON);
|
||||
MemoryRecordStore.resetStatistics();
|
||||
possibleValueTranslator.primePvsCache(personTable, personRecords);
|
||||
assertEquals(1, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN), "Should only run 1 query");
|
||||
possibleValueTranslator.translatePossibleValue(shapeField, 1);
|
||||
possibleValueTranslator.translatePossibleValue(shapeField, 2);
|
||||
assertEquals(1, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN), "Should only run 1 query");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Make sure that if we have 2 different PVS's pointed at the same 1 table,
|
||||
** that we avoid re-doing queries, and that we actually get different (formatted) values.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPossibleValueTableMultiplePvsForATable() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
QTableMetaData shapeTable = qInstance.getTable(TestUtils.TABLE_NAME_SHAPE);
|
||||
QTableMetaData personTable = qInstance.getTable(TestUtils.TABLE_NAME_PERSON);
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// define a second version of the Shape PVS, with a unique format //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
qInstance.addPossibleValueSource(new QPossibleValueSource()
|
||||
.withName("shapeV2")
|
||||
.withType(QPossibleValueSourceType.TABLE)
|
||||
.withTableName(TestUtils.TABLE_NAME_SHAPE)
|
||||
.withValueFormat("%d: %s")
|
||||
.withValueFields(List.of("id", "label"))
|
||||
);
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// use that PVS in a new column on the person table //
|
||||
//////////////////////////////////////////////////////
|
||||
personTable.addField(new QFieldMetaData("currentShapeId", QFieldType.INTEGER)
|
||||
.withPossibleValueSourceName("shapeV2")
|
||||
);
|
||||
|
||||
TestUtils.insertDefaultShapes(qInstance);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// define a list of persons pointing at those shapes //
|
||||
///////////////////////////////////////////////////////
|
||||
List<QRecord> personRecords = List.of(
|
||||
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 1).withValue("currentShapeId", 2),
|
||||
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 1).withValue("currentShapeId", 3),
|
||||
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 2).withValue("currentShapeId", 3),
|
||||
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 2).withValue("currentShapeId", 3)
|
||||
);
|
||||
|
||||
/////////////////////////
|
||||
// translate the PVS's //
|
||||
/////////////////////////
|
||||
MemoryRecordStore.setCollectStatistics(true);
|
||||
new QPossibleValueTranslator(qInstance, new QSession()).translatePossibleValuesInRecords(personTable, personRecords);
|
||||
|
||||
/////////////////////////////////
|
||||
// assert only 1 query was ran //
|
||||
/////////////////////////////////
|
||||
assertEquals(1, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN), "Should only run 1 query");
|
||||
|
||||
////////////////////////////////////////
|
||||
// assert expected values and formats //
|
||||
////////////////////////////////////////
|
||||
assertEquals("Triangle", personRecords.get(0).getDisplayValue("favoriteShapeId"));
|
||||
assertEquals("2: Square", personRecords.get(0).getDisplayValue("currentShapeId"));
|
||||
assertEquals("Triangle", personRecords.get(1).getDisplayValue("favoriteShapeId"));
|
||||
assertEquals("3: Circle", personRecords.get(1).getDisplayValue("currentShapeId"));
|
||||
assertEquals("Square", personRecords.get(2).getDisplayValue("favoriteShapeId"));
|
||||
assertEquals("3: Circle", personRecords.get(2).getDisplayValue("currentShapeId"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Make sure that if we have 2 different PVS's pointed at the same 1 table,
|
||||
** that we avoid re-doing queries, and that we actually get different (formatted) values.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testCustomPossibleValue() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
QTableMetaData personTable = qInstance.getTable(TestUtils.TABLE_NAME_PERSON);
|
||||
String fieldName = "customValue";
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// define a list of persons with values in the custom field //
|
||||
//////////////////////////////////////////////////////////////
|
||||
List<QRecord> personRecords = List.of(
|
||||
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue(fieldName, 1),
|
||||
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue(fieldName, 2),
|
||||
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue(fieldName, "Buckle my shoe")
|
||||
);
|
||||
|
||||
/////////////////////////
|
||||
// translate the PVS's //
|
||||
/////////////////////////
|
||||
new QPossibleValueTranslator(qInstance, new QSession()).translatePossibleValuesInRecords(personTable, personRecords);
|
||||
|
||||
////////////////////////////////////////
|
||||
// assert expected values and formats //
|
||||
////////////////////////////////////////
|
||||
assertEquals("Custom[1]", personRecords.get(0).getDisplayValue(fieldName));
|
||||
assertEquals("Custom[2]", personRecords.get(1).getDisplayValue(fieldName));
|
||||
assertEquals("Custom[Buckle my shoe]", personRecords.get(2).getDisplayValue(fieldName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testSetDisplayValuesInRecords()
|
||||
{
|
||||
QTableMetaData table = TestUtils.defineTablePerson();
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// first, make sure it doesn't crash with null or empty inputs //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
QPossibleValueTranslator possibleValueTranslator = new QPossibleValueTranslator(TestUtils.defineInstance(), new QSession());
|
||||
possibleValueTranslator.translatePossibleValuesInRecords(table, null);
|
||||
possibleValueTranslator.translatePossibleValuesInRecords(table, Collections.emptyList());
|
||||
|
||||
List<QRecord> records = List.of(
|
||||
new QRecord()
|
||||
.withValue("firstName", "Tim")
|
||||
.withValue("lastName", "Chamberlain")
|
||||
.withValue("price", new BigDecimal("3.50"))
|
||||
.withValue("homeStateId", 1),
|
||||
new QRecord()
|
||||
.withValue("firstName", "Tyler")
|
||||
.withValue("lastName", "Samples")
|
||||
.withValue("price", new BigDecimal("174999.99"))
|
||||
.withValue("homeStateId", 2)
|
||||
);
|
||||
|
||||
possibleValueTranslator.translatePossibleValuesInRecords(table, records);
|
||||
|
||||
assertNull(records.get(0).getRecordLabel()); // regular display stuff NOT done by PVS translator
|
||||
assertNull(records.get(0).getDisplayValue("price"));
|
||||
|
||||
assertEquals("IL", records.get(0).getDisplayValue("homeStateId"));
|
||||
assertEquals("MO", records.get(1).getDisplayValue("homeStateId"));
|
||||
}
|
||||
|
||||
}
|
@ -30,6 +30,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
||||
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.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
@ -47,24 +48,26 @@ class QValueFormatterTest
|
||||
@Test
|
||||
void testFormatValue()
|
||||
{
|
||||
assertNull(QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), null));
|
||||
QValueFormatter qValueFormatter = new QValueFormatter();
|
||||
|
||||
assertEquals("1", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), 1));
|
||||
assertEquals("1,000", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), 1000));
|
||||
assertEquals("1000", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(null), 1000));
|
||||
assertEquals("$1,000.00", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.CURRENCY), 1000));
|
||||
assertEquals("1,000.00", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.DECIMAL2_COMMAS), 1000));
|
||||
assertEquals("1000.00", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.DECIMAL2), 1000));
|
||||
assertNull(qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), null));
|
||||
|
||||
assertEquals("1", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), new BigDecimal("1")));
|
||||
assertEquals("1,000", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), new BigDecimal("1000")));
|
||||
assertEquals("1000", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.STRING), new BigDecimal("1000")));
|
||||
assertEquals("1000", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.STRING), 1000));
|
||||
assertEquals("1", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), 1));
|
||||
assertEquals("1,000", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), 1000));
|
||||
assertEquals("1000", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(null), 1000));
|
||||
assertEquals("$1,000.00", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.CURRENCY), 1000));
|
||||
assertEquals("1,000.00", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.DECIMAL2_COMMAS), 1000));
|
||||
assertEquals("1000.00", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.DECIMAL2), 1000));
|
||||
|
||||
assertEquals("1", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), new BigDecimal("1")));
|
||||
assertEquals("1,000", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), new BigDecimal("1000")));
|
||||
assertEquals("1000", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.STRING), new BigDecimal("1000")));
|
||||
assertEquals("1000", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.STRING), 1000));
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// this one flows through the exceptional cases //
|
||||
//////////////////////////////////////////////////
|
||||
assertEquals("1000.01", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), new BigDecimal("1000.01")));
|
||||
assertEquals("1000.01", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), new BigDecimal("1000.01")));
|
||||
}
|
||||
|
||||
|
||||
@ -75,40 +78,42 @@ class QValueFormatterTest
|
||||
@Test
|
||||
void testFormatRecordLabel()
|
||||
{
|
||||
QTableMetaData table = new QTableMetaData().withRecordLabelFormat("%s %s").withRecordLabelFields(List.of("firstName", "lastName"));
|
||||
assertEquals("Darin Kelkhoff", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("firstName", "Darin").withValue("lastName", "Kelkhoff")));
|
||||
assertEquals("Darin ", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("firstName", "Darin")));
|
||||
assertEquals("Darin ", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("firstName", "Darin").withValue("lastName", null)));
|
||||
QValueFormatter qValueFormatter = new QValueFormatter();
|
||||
|
||||
table = new QTableMetaData().withRecordLabelFormat("%s " + DisplayFormat.CURRENCY).withRecordLabelFields(List.of("firstName", "price"));
|
||||
assertEquals("Darin $10,000.00", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("firstName", "Darin").withValue("price", new BigDecimal(10000))));
|
||||
QTableMetaData table = new QTableMetaData().withRecordLabelFormat("%s %s").withRecordLabelFields(List.of("firstName", "lastName"));
|
||||
assertEquals("Darin Kelkhoff", qValueFormatter.formatRecordLabel(table, new QRecord().withValue("firstName", "Darin").withValue("lastName", "Kelkhoff")));
|
||||
assertEquals("Darin ", qValueFormatter.formatRecordLabel(table, new QRecord().withValue("firstName", "Darin")));
|
||||
assertEquals("Darin ", qValueFormatter.formatRecordLabel(table, new QRecord().withValue("firstName", "Darin").withValue("lastName", null)));
|
||||
|
||||
table = new QTableMetaData().withRecordLabelFormat("%s " + DisplayFormat.CURRENCY).withRecordLabelFields("firstName", "price");
|
||||
assertEquals("Darin $10,000.00", qValueFormatter.formatRecordLabel(table, new QRecord().withValue("firstName", "Darin").withValue("price", new BigDecimal(10000))));
|
||||
|
||||
table = new QTableMetaData().withRecordLabelFormat(DisplayFormat.DEFAULT).withRecordLabelFields(List.of("id"));
|
||||
assertEquals("123456", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("id", "123456")));
|
||||
assertEquals("123456", qValueFormatter.formatRecordLabel(table, new QRecord().withValue("id", "123456")));
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// exceptional flow: no recordLabelFormat specified //
|
||||
///////////////////////////////////////////////////////
|
||||
table = new QTableMetaData().withPrimaryKeyField("id");
|
||||
assertEquals("42", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("id", 42)));
|
||||
assertEquals("42", qValueFormatter.formatRecordLabel(table, new QRecord().withValue("id", 42)));
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// exceptional flow: no fields for the format //
|
||||
/////////////////////////////////////////////////
|
||||
table = new QTableMetaData().withRecordLabelFormat("%s %s").withPrimaryKeyField("id");
|
||||
assertEquals("128", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("id", 128)));
|
||||
assertEquals("128", qValueFormatter.formatRecordLabel(table, new QRecord().withValue("id", 128)));
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// exceptional flow: not enough fields for the format //
|
||||
/////////////////////////////////////////////////////////
|
||||
table = new QTableMetaData().withRecordLabelFormat("%s %s").withRecordLabelFields(List.of("a")).withPrimaryKeyField("id");
|
||||
assertEquals("256", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("a", 47).withValue("id", 256)));
|
||||
table = new QTableMetaData().withRecordLabelFormat("%s %s").withRecordLabelFields("a").withPrimaryKeyField("id");
|
||||
assertEquals("256", qValueFormatter.formatRecordLabel(table, new QRecord().withValue("a", 47).withValue("id", 256)));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// exceptional flow (kinda): too many fields for the format (just get the ones that are in the format) //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
table = new QTableMetaData().withRecordLabelFormat("%s %s").withRecordLabelFields(List.of("a", "b", "c")).withPrimaryKeyField("id");
|
||||
assertEquals("47 48", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("a", 47).withValue("b", 48).withValue("c", 49).withValue("id", 256)));
|
||||
assertEquals("47 48", qValueFormatter.formatRecordLabel(table, new QRecord().withValue("a", 47).withValue("b", 48).withValue("c", 49).withValue("id", 256)));
|
||||
}
|
||||
|
||||
|
||||
@ -121,40 +126,46 @@ class QValueFormatterTest
|
||||
{
|
||||
QTableMetaData table = new QTableMetaData()
|
||||
.withRecordLabelFormat("%s %s")
|
||||
.withRecordLabelFields(List.of("firstName", "lastName"))
|
||||
.withRecordLabelFields("firstName", "lastName")
|
||||
.withField(new QFieldMetaData("firstName", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("lastName", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("price", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY))
|
||||
.withField(new QFieldMetaData("quantity", QFieldType.INTEGER).withDisplayFormat(DisplayFormat.COMMAS));
|
||||
.withField(new QFieldMetaData("quantity", QFieldType.INTEGER).withDisplayFormat(DisplayFormat.COMMAS))
|
||||
.withField(new QFieldMetaData("homeStateId", QFieldType.INTEGER).withPossibleValueSourceName(TestUtils.POSSIBLE_VALUE_SOURCE_STATE));
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// first, make sure it doesn't crash with null or empty inputs //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
QValueFormatter.setDisplayValuesInRecords(table, null);
|
||||
QValueFormatter.setDisplayValuesInRecords(table, Collections.emptyList());
|
||||
QValueFormatter qValueFormatter = new QValueFormatter();
|
||||
qValueFormatter.setDisplayValuesInRecords(table, null);
|
||||
qValueFormatter.setDisplayValuesInRecords(table, Collections.emptyList());
|
||||
|
||||
List<QRecord> records = List.of(
|
||||
new QRecord()
|
||||
.withValue("firstName", "Tim")
|
||||
.withValue("lastName", "Chamberlain")
|
||||
.withValue("price", new BigDecimal("3.50"))
|
||||
.withValue("quantity", 1701),
|
||||
.withValue("quantity", 1701)
|
||||
.withValue("homeStateId", 1),
|
||||
new QRecord()
|
||||
.withValue("firstName", "Tyler")
|
||||
.withValue("lastName", "Samples")
|
||||
.withValue("price", new BigDecimal("174999.99"))
|
||||
.withValue("quantity", 47)
|
||||
.withValue("homeStateId", 2)
|
||||
);
|
||||
|
||||
QValueFormatter.setDisplayValuesInRecords(table, records);
|
||||
qValueFormatter.setDisplayValuesInRecords(table, records);
|
||||
|
||||
assertEquals("Tim Chamberlain", records.get(0).getRecordLabel());
|
||||
assertEquals("$3.50", records.get(0).getDisplayValue("price"));
|
||||
assertEquals("1,701", records.get(0).getDisplayValue("quantity"));
|
||||
assertEquals("1", records.get(0).getDisplayValue("homeStateId")); // PVS NOT translated by this class.
|
||||
|
||||
assertEquals("Tyler Samples", records.get(1).getRecordLabel());
|
||||
assertEquals("$174,999.99", records.get(1).getDisplayValue("price"));
|
||||
assertEquals("47", records.get(1).getDisplayValue("quantity"));
|
||||
assertEquals("2", records.get(1).getDisplayValue("homeStateId")); // PVS NOT translated by this class.
|
||||
}
|
||||
|
||||
}
|
@ -31,6 +31,7 @@ import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
@ -281,4 +282,65 @@ class CsvToQRecordAdapterTest
|
||||
// todo - this is what the method header comment means when it says we don't handle all cases well...
|
||||
// Assertions.assertEquals(List.of("A", "B", "C", "C 2", "C 3"), csvToQRecordAdapter.makeHeadersUnique(List.of("A", "B", "C 2", "C", "C 3")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testByteOrderMarker()
|
||||
{
|
||||
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// note - there's a zero-width non-breaking-space character (0xFEFF or some-such) //
|
||||
// at the start of this string!! You may not be able to see it, depending on where you view this file. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> records = csvToQRecordAdapter.buildRecordsFromCsv("""
|
||||
id,firstName
|
||||
1,John""", TestUtils.defineTablePerson(), null);
|
||||
|
||||
assertEquals(1, records.get(0).getValueInteger("id"));
|
||||
assertEquals("John", records.get(0).getValueString("firstName"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fix an IndexOutOfBounds that we used to throw.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTooFewBodyColumns()
|
||||
{
|
||||
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
||||
List<QRecord> records = csvToQRecordAdapter.buildRecordsFromCsv("""
|
||||
id,firstName,lastName
|
||||
1,John""", TestUtils.defineTablePerson(), null);
|
||||
|
||||
assertEquals(1, records.get(0).getValueInteger("id"));
|
||||
assertEquals("John", records.get(0).getValueString("firstName"));
|
||||
assertNull(records.get(0).getValueString("lastName"));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testTooFewColumnsIndexMapping()
|
||||
{
|
||||
int index = 1;
|
||||
QIndexBasedFieldMapping mapping = new QIndexBasedFieldMapping()
|
||||
.withMapping("id", index++)
|
||||
.withMapping("firstName", index++)
|
||||
.withMapping("lastName", index++);
|
||||
|
||||
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
||||
List<QRecord> records = csvToQRecordAdapter.buildRecordsFromCsv("1,John", TestUtils.defineTablePerson(), mapping);
|
||||
|
||||
assertEquals(1, records.get(0).getValueInteger("id"));
|
||||
assertEquals("John", records.get(0).getValueString("firstName"));
|
||||
assertNull(records.get(0).getValueString("lastName"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,10 +22,10 @@
|
||||
package com.kingsrook.qqq.backend.core.instances;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
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.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
@ -130,6 +130,21 @@ class QInstanceEnricherTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testNameToLabel()
|
||||
{
|
||||
assertEquals("Address 2", QInstanceEnricher.nameToLabel("address2"));
|
||||
assertEquals("Field 20", QInstanceEnricher.nameToLabel("field20"));
|
||||
assertEquals("Something USA", QInstanceEnricher.nameToLabel("somethingUSA"));
|
||||
assertEquals("Number 1 Dad", QInstanceEnricher.nameToLabel("number1Dad"));
|
||||
assertEquals("Number 417 Dad", QInstanceEnricher.nameToLabel("number417Dad"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -146,4 +161,28 @@ class QInstanceEnricherTest
|
||||
assertEquals("tla_and_another_tla", QInstanceEnricher.inferBackendName("TLAAndAnotherTLA"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testInferredRecordLabelFormat()
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
QTableMetaData table = qInstance.getTable("person").withRecordLabelFormat(null).withRecordLabelFields(new ArrayList<>());
|
||||
new QInstanceEnricher().enrich(qInstance);
|
||||
assertNull(table.getRecordLabelFormat());
|
||||
|
||||
qInstance = TestUtils.defineInstance();
|
||||
table = qInstance.getTable("person").withRecordLabelFormat(null).withRecordLabelFields("firstName");
|
||||
new QInstanceEnricher().enrich(qInstance);
|
||||
assertEquals("%s", table.getRecordLabelFormat());
|
||||
|
||||
qInstance = TestUtils.defineInstance();
|
||||
table = qInstance.getTable("person").withRecordLabelFormat(null).withRecordLabelFields("firstName", "lastName");
|
||||
new QInstanceEnricher().enrich(qInstance);
|
||||
assertEquals("%s %s", table.getRecordLabelFormat());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,16 +22,25 @@
|
||||
package com.kingsrook.qqq.backend.core.instances;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||
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.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
@ -115,7 +124,8 @@ class QInstanceValidatorTest
|
||||
qInstance.setTables(null);
|
||||
qInstance.setProcesses(null);
|
||||
},
|
||||
"At least 1 table must be defined");
|
||||
"At least 1 table must be defined",
|
||||
"Unrecognized table shape for possibleValueSource shape");
|
||||
}
|
||||
|
||||
|
||||
@ -132,7 +142,8 @@ class QInstanceValidatorTest
|
||||
qInstance.setTables(new HashMap<>());
|
||||
qInstance.setProcesses(new HashMap<>());
|
||||
},
|
||||
"At least 1 table must be defined");
|
||||
"At least 1 table must be defined",
|
||||
"Unrecognized table shape for possibleValueSource shape");
|
||||
}
|
||||
|
||||
|
||||
@ -150,10 +161,13 @@ class QInstanceValidatorTest
|
||||
qInstance.getTable("person").setName("notPerson");
|
||||
qInstance.getBackend("default").setName("notDefault");
|
||||
qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE).setName("notGreetPeople");
|
||||
qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_STATE).setName("notStates");
|
||||
},
|
||||
"Inconsistent naming for table",
|
||||
"Inconsistent naming for backend",
|
||||
"Inconsistent naming for process");
|
||||
"Inconsistent naming for process",
|
||||
"Inconsistent naming for possibleValueSource"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -184,6 +198,18 @@ class QInstanceValidatorTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_validateTableBadRecordFormatField()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withRecordLabelFields("notAField"),
|
||||
"not a field");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Test that if a process specifies a table that doesn't exist, that it fails.
|
||||
**
|
||||
@ -245,6 +271,138 @@ class QInstanceValidatorTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTableCustomizers()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference()),
|
||||
"missing a code reference name", "missing a code type");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(null, QCodeType.JAVA, null)),
|
||||
"missing a code reference name");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference("", QCodeType.JAVA, null)),
|
||||
"missing a code reference name");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference("Test", null, null)),
|
||||
"missing a code type");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference("Test", QCodeType.JAVA, QCodeUsage.CUSTOMIZER)),
|
||||
"Class for CodeReference could not be found");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerWithNoVoidConstructor.class, QCodeUsage.CUSTOMIZER)),
|
||||
"Instance of CodeReference could not be created");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerThatIsNotAFunction.class, QCodeUsage.CUSTOMIZER)),
|
||||
"CodeReference could not be casted");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerFunctionWithIncorrectTypeParameters.class, QCodeUsage.CUSTOMIZER)),
|
||||
"Error validating customizer type parameters");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerFunctionWithIncorrectTypeParameter1.class, QCodeUsage.CUSTOMIZER)),
|
||||
"Error validating customizer type parameters");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerFunctionWithIncorrectTypeParameter2.class, QCodeUsage.CUSTOMIZER)),
|
||||
"Error validating customizer type parameters");
|
||||
|
||||
assertValidationSuccess((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerValid.class, QCodeUsage.CUSTOMIZER)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class CustomizerWithNoVoidConstructor
|
||||
{
|
||||
public CustomizerWithNoVoidConstructor(boolean b)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class CustomizerWithOnlyPrivateConstructor
|
||||
{
|
||||
private CustomizerWithOnlyPrivateConstructor()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class CustomizerThatIsNotAFunction
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class CustomizerFunctionWithIncorrectTypeParameters implements Function<String, String>
|
||||
{
|
||||
@Override
|
||||
public String apply(String s)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class CustomizerFunctionWithIncorrectTypeParameter1 implements Function<String, QRecord>
|
||||
{
|
||||
@Override
|
||||
public QRecord apply(String s)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class CustomizerFunctionWithIncorrectTypeParameter2 implements Function<QRecord, String>
|
||||
{
|
||||
@Override
|
||||
public String apply(QRecord s)
|
||||
{
|
||||
return "Test";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class CustomizerValid implements Function<QRecord, QRecord>
|
||||
{
|
||||
@Override
|
||||
public QRecord apply(QRecord record)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Test that if a field specifies a backend that doesn't exist, that it fails.
|
||||
**
|
||||
@ -252,7 +410,7 @@ class QInstanceValidatorTest
|
||||
@Test
|
||||
public void test_validateFieldWithMissingPossibleValueSource()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").getField("homeState").setPossibleValueSourceName("not a real possible value source"),
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").getField("homeStateId").setPossibleValueSourceName("not a real possible value source"),
|
||||
"Unrecognized possibleValueSourceName");
|
||||
}
|
||||
|
||||
@ -319,6 +477,7 @@ class QInstanceValidatorTest
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -376,6 +535,7 @@ class QInstanceValidatorTest
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -391,6 +551,7 @@ class QInstanceValidatorTest
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -408,6 +569,86 @@ class QInstanceValidatorTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPossibleValueSourceMissingType()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_STATE).setType(null),
|
||||
"Missing type for possibleValueSource");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPossibleValueSourceMisConfiguredEnum()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> {
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_STATE);
|
||||
possibleValueSource.setTableName("person");
|
||||
possibleValueSource.setCustomCodeReference(new QCodeReference());
|
||||
possibleValueSource.setEnumValues(null);
|
||||
},
|
||||
"should not have a tableName",
|
||||
"should not have a customCodeReference",
|
||||
"is missing enum values");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_STATE).setEnumValues(new ArrayList<>()),
|
||||
"is missing enum values");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPossibleValueSourceMisConfiguredTable()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> {
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE);
|
||||
possibleValueSource.setTableName(null);
|
||||
possibleValueSource.setCustomCodeReference(new QCodeReference());
|
||||
possibleValueSource.setEnumValues(List.of(new QPossibleValue<>("test")));
|
||||
},
|
||||
"should not have enum values",
|
||||
"should not have a customCodeReference",
|
||||
"is missing a tableName");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE).setTableName("Not a table"),
|
||||
"Unrecognized table");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPossibleValueSourceMisConfiguredCustom()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> {
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_CUSTOM);
|
||||
possibleValueSource.setTableName("person");
|
||||
possibleValueSource.setCustomCodeReference(null);
|
||||
possibleValueSource.setEnumValues(List.of(new QPossibleValue<>("test")));
|
||||
},
|
||||
"should not have enum values",
|
||||
"should not have a tableName",
|
||||
"is missing a customCodeReference");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_CUSTOM).setCustomCodeReference(new QCodeReference()),
|
||||
"not a possibleValueProvider",
|
||||
"missing a code reference name",
|
||||
"missing a code type");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Run a little setup code on a qInstance; then validate it, and assert that it
|
||||
** failed validation with reasons that match the supplied vararg-reasons (but allow
|
||||
@ -448,7 +689,8 @@ class QInstanceValidatorTest
|
||||
{
|
||||
if(!allowExtraReasons)
|
||||
{
|
||||
assertEquals(reasons.length, e.getReasons().size(), "Expected number of validation failure reasons\nExpected: " + String.join(",", reasons) + "\nActual: " + e.getReasons());
|
||||
int noOfReasons = e.getReasons() == null ? 0 : e.getReasons().size();
|
||||
assertEquals(reasons.length, noOfReasons, "Expected number of validation failure reasons.\nExpected reasons: " + String.join(",", reasons) + "\nActual reasons: " + e.getReasons());
|
||||
}
|
||||
|
||||
for(String reason : reasons)
|
||||
@ -460,6 +702,25 @@ class QInstanceValidatorTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Assert that an instance is valid!
|
||||
*******************************************************************************/
|
||||
private void assertValidationSuccess(Consumer<QInstance> setup)
|
||||
{
|
||||
try
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
setup.accept(qInstance);
|
||||
new QInstanceValidator().validate(qInstance);
|
||||
}
|
||||
catch(QInstanceValidationException e)
|
||||
{
|
||||
fail("Expected no validation errors, but received: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** utility method for asserting that a specific reason string is found within
|
||||
** the list of reasons in the QInstanceValidationException.
|
||||
|
@ -0,0 +1,334 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.modules.backend.implementations.memory;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
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.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for MemoryBackendModule
|
||||
*******************************************************************************/
|
||||
class MemoryBackendModuleTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
@AfterEach
|
||||
void beforeAndAfter()
|
||||
{
|
||||
MemoryRecordStore.getInstance().reset();
|
||||
MemoryRecordStore.resetStatistics();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testFullCRUD() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_SHAPE);
|
||||
QSession session = new QSession();
|
||||
|
||||
/////////////////////////
|
||||
// do an initial count //
|
||||
/////////////////////////
|
||||
CountInput countInput = new CountInput(qInstance);
|
||||
countInput.setSession(session);
|
||||
countInput.setTableName(table.getName());
|
||||
assertEquals(0, new CountAction().execute(countInput).getCount());
|
||||
|
||||
//////////////////
|
||||
// do an insert //
|
||||
//////////////////
|
||||
InsertInput insertInput = new InsertInput(qInstance);
|
||||
insertInput.setSession(session);
|
||||
insertInput.setTableName(table.getName());
|
||||
insertInput.setRecords(getTestRecords(table));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
assertEquals(3, insertOutput.getRecords().size());
|
||||
assertTrue(insertOutput.getRecords().stream().allMatch(r -> r.getValue("id") != null));
|
||||
assertTrue(insertOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1)));
|
||||
assertTrue(insertOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2)));
|
||||
assertTrue(insertOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3)));
|
||||
|
||||
////////////////
|
||||
// do a query //
|
||||
////////////////
|
||||
QueryInput queryInput = new QueryInput(qInstance);
|
||||
queryInput.setSession(session);
|
||||
queryInput.setTableName(table.getName());
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
assertEquals(3, queryOutput.getRecords().size());
|
||||
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValue("id") != null));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1)));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2)));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3)));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("name").equals("My Triangle")));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("name").equals("Your Square")));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("name").equals("Some Circle")));
|
||||
|
||||
assertEquals(3, new CountAction().execute(countInput).getCount());
|
||||
|
||||
//////////////////
|
||||
// do an update //
|
||||
//////////////////
|
||||
UpdateInput updateInput = new UpdateInput(qInstance);
|
||||
updateInput.setSession(session);
|
||||
updateInput.setTableName(table.getName());
|
||||
updateInput.setRecords(List.of(
|
||||
new QRecord()
|
||||
.withTableName(table.getName())
|
||||
.withValue("id", 1)
|
||||
.withValue("name", "Not My Triangle any more"),
|
||||
new QRecord()
|
||||
.withTableName(table.getName())
|
||||
.withValue("id", 3)
|
||||
.withValue("type", "ellipse")
|
||||
));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
assertEquals(2, updateOutput.getRecords().size());
|
||||
|
||||
queryOutput = new QueryAction().execute(queryInput);
|
||||
assertEquals(3, queryOutput.getRecords().size());
|
||||
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("name").equals("My Triangle")));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("name").equals("Not My Triangle any more")));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("type").equals("ellipse")));
|
||||
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("type").equals("circle")));
|
||||
|
||||
assertEquals(3, new CountAction().execute(countInput).getCount());
|
||||
|
||||
/////////////////////////
|
||||
// do a filtered query //
|
||||
/////////////////////////
|
||||
queryInput = new QueryInput(qInstance);
|
||||
queryInput.setSession(session);
|
||||
queryInput.setTableName(table.getName());
|
||||
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("id", QCriteriaOperator.IN, List.of(1, 3))));
|
||||
queryOutput = new QueryAction().execute(queryInput);
|
||||
assertEquals(2, queryOutput.getRecords().size());
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1)));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3)));
|
||||
|
||||
/////////////////////////
|
||||
// do a filtered count //
|
||||
/////////////////////////
|
||||
countInput.setFilter(queryInput.getFilter());
|
||||
assertEquals(2, new CountAction().execute(countInput).getCount());
|
||||
|
||||
/////////////////
|
||||
// do a delete //
|
||||
/////////////////
|
||||
DeleteInput deleteInput = new DeleteInput(qInstance);
|
||||
deleteInput.setSession(session);
|
||||
deleteInput.setTableName(table.getName());
|
||||
deleteInput.setPrimaryKeys(List.of(1, 2));
|
||||
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);
|
||||
assertEquals(2, deleteOutput.getDeletedRecordCount());
|
||||
|
||||
assertEquals(1, new CountAction().execute(countInput).getCount());
|
||||
|
||||
queryOutput = new QueryAction().execute(queryInput);
|
||||
assertEquals(1, queryOutput.getRecords().size());
|
||||
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueInteger("id").equals(1)));
|
||||
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueInteger("id").equals(2)));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testSerials() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_SHAPE);
|
||||
QSession session = new QSession();
|
||||
|
||||
//////////////////
|
||||
// do an insert //
|
||||
//////////////////
|
||||
InsertInput insertInput = new InsertInput(qInstance);
|
||||
insertInput.setSession(session);
|
||||
insertInput.setTableName(table.getName());
|
||||
insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("name", "Shape 1")));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("name", "Shape 2")));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("name", "Shape 3")));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
QueryInput queryInput = new QueryInput(qInstance);
|
||||
queryInput.setSession(new QSession());
|
||||
queryInput.setTableName(table.getName());
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1)));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2)));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3)));
|
||||
|
||||
insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("id", 4).withValue("name", "Shape 4")));
|
||||
new InsertAction().execute(insertInput);
|
||||
queryOutput = new QueryAction().execute(queryInput);
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(4)));
|
||||
|
||||
insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("id", 6).withValue("name", "Shape 6")));
|
||||
new InsertAction().execute(insertInput);
|
||||
queryOutput = new QueryAction().execute(queryInput);
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(6)));
|
||||
|
||||
insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("name", "Shape 7")));
|
||||
new InsertAction().execute(insertInput);
|
||||
queryOutput = new QueryAction().execute(queryInput);
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(7)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<QRecord> getTestRecords(QTableMetaData table)
|
||||
{
|
||||
return List.of(
|
||||
new QRecord()
|
||||
.withTableName(table.getName())
|
||||
.withValue("name", "My Triangle")
|
||||
.withValue("type", "triangle")
|
||||
.withValue("noOfSides", 3)
|
||||
.withValue("isPolygon", true),
|
||||
new QRecord()
|
||||
.withTableName(table.getName())
|
||||
.withValue("name", "Your Square")
|
||||
.withValue("type", "square")
|
||||
.withValue("noOfSides", 4)
|
||||
.withValue("isPolygon", true),
|
||||
new QRecord()
|
||||
.withTableName(table.getName())
|
||||
.withValue("name", "Some Circle")
|
||||
.withValue("type", "circle")
|
||||
.withValue("noOfSides", null)
|
||||
.withValue("isPolygon", false)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testCustomizer() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_SHAPE);
|
||||
QSession session = new QSession();
|
||||
|
||||
///////////////////////////////////
|
||||
// add a customizer to the table //
|
||||
///////////////////////////////////
|
||||
table.withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(ShapeTestCustomizer.class, QCodeUsage.CUSTOMIZER));
|
||||
|
||||
//////////////////
|
||||
// do an insert //
|
||||
//////////////////
|
||||
InsertInput insertInput = new InsertInput(qInstance);
|
||||
insertInput.setSession(session);
|
||||
insertInput.setTableName(table.getName());
|
||||
insertInput.setRecords(getTestRecords(table));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// do a query - assert that the customizer did stuff //
|
||||
///////////////////////////////////////////////////////
|
||||
ShapeTestCustomizer.executionCount = 0;
|
||||
QueryInput queryInput = new QueryInput(qInstance);
|
||||
queryInput.setSession(session);
|
||||
queryInput.setTableName(table.getName());
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
assertEquals(3, queryOutput.getRecords().size());
|
||||
assertEquals(3, ShapeTestCustomizer.executionCount);
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1) && r.getValueInteger("tenTimesId").equals(10)));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2) && r.getValueInteger("tenTimesId").equals(20)));
|
||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3) && r.getValueInteger("tenTimesId").equals(30)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class ShapeTestCustomizer implements Function<QRecord, QRecord>
|
||||
{
|
||||
static int executionCount = 0;
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public QRecord apply(QRecord record)
|
||||
{
|
||||
executionCount++;
|
||||
record.setValue("tenTimesId", record.getValueInteger("id") * 10);
|
||||
return (record);
|
||||
}
|
||||
}
|
||||
}
|
@ -22,12 +22,15 @@
|
||||
package com.kingsrook.qqq.backend.core.utils;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.AddAge;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.GetAgeStatistics;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@ -40,6 +43,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||
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.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
@ -52,6 +56,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.MockAuthenticationModule;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.mock.MockBackendModule;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.basic.BasicETLProcess;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamed.StreamedETLProcess;
|
||||
@ -65,12 +70,14 @@ import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackend
|
||||
public class TestUtils
|
||||
{
|
||||
public static final String DEFAULT_BACKEND_NAME = "default";
|
||||
public static final String MEMORY_BACKEND_NAME = "memory";
|
||||
|
||||
public static final String APP_NAME_GREETINGS = "greetingsApp";
|
||||
public static final String APP_NAME_PEOPLE = "peopleApp";
|
||||
public static final String APP_NAME_MISCELLANEOUS = "miscellaneous";
|
||||
|
||||
public static final String TABLE_NAME_PERSON = "person";
|
||||
public static final String TABLE_NAME_SHAPE = "shape";
|
||||
|
||||
public static final String PROCESS_NAME_GREET_PEOPLE = "greet";
|
||||
public static final String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive";
|
||||
@ -78,6 +85,10 @@ public class TestUtils
|
||||
public static final String TABLE_NAME_PERSON_FILE = "personFile";
|
||||
public static final String TABLE_NAME_ID_AND_NAME_ONLY = "idAndNameOnly";
|
||||
|
||||
public static final String POSSIBLE_VALUE_SOURCE_STATE = "state"; // enum-type
|
||||
public static final String POSSIBLE_VALUE_SOURCE_SHAPE = "shape"; // table-type
|
||||
public static final String POSSIBLE_VALUE_SOURCE_CUSTOM = "custom"; // custom-type
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -89,12 +100,16 @@ public class TestUtils
|
||||
QInstance qInstance = new QInstance();
|
||||
qInstance.setAuthentication(defineAuthentication());
|
||||
qInstance.addBackend(defineBackend());
|
||||
qInstance.addBackend(defineMemoryBackend());
|
||||
|
||||
qInstance.addTable(defineTablePerson());
|
||||
qInstance.addTable(definePersonFileTable());
|
||||
qInstance.addTable(defineTableIdAndNameOnly());
|
||||
qInstance.addTable(defineTableShape());
|
||||
|
||||
qInstance.addPossibleValueSource(defineStatesPossibleValueSource());
|
||||
qInstance.addPossibleValueSource(defineShapePossibleValueSource());
|
||||
qInstance.addPossibleValueSource(defineCustomPossibleValueSource());
|
||||
|
||||
qInstance.addProcess(defineProcessGreetPeople());
|
||||
qInstance.addProcess(defineProcessGreetPeopleInteractive());
|
||||
@ -104,8 +119,6 @@ public class TestUtils
|
||||
|
||||
defineApps(qInstance);
|
||||
|
||||
System.out.println(new QInstanceAdapter().qInstanceToJson(qInstance));
|
||||
|
||||
return (qInstance);
|
||||
}
|
||||
|
||||
@ -139,12 +152,40 @@ public class TestUtils
|
||||
** Define the "states" possible value source used in standard tests
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QPossibleValueSource<String> defineStatesPossibleValueSource()
|
||||
private static QPossibleValueSource defineStatesPossibleValueSource()
|
||||
{
|
||||
return new QPossibleValueSource<String>()
|
||||
.withName("state")
|
||||
return new QPossibleValueSource()
|
||||
.withName(POSSIBLE_VALUE_SOURCE_STATE)
|
||||
.withType(QPossibleValueSourceType.ENUM)
|
||||
.withEnumValues(List.of("IL", "MO"));
|
||||
.withEnumValues(List.of(new QPossibleValue<>(1, "IL"), new QPossibleValue<>(2, "MO")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the "shape" possible value source used in standard tests
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QPossibleValueSource defineShapePossibleValueSource()
|
||||
{
|
||||
return new QPossibleValueSource()
|
||||
.withName(POSSIBLE_VALUE_SOURCE_SHAPE)
|
||||
.withType(QPossibleValueSourceType.TABLE)
|
||||
.withTableName(TABLE_NAME_SHAPE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the "custom" possible value source used in standard tests
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QPossibleValueSource defineCustomPossibleValueSource()
|
||||
{
|
||||
return new QPossibleValueSource()
|
||||
.withName(POSSIBLE_VALUE_SOURCE_CUSTOM)
|
||||
.withType(QPossibleValueSourceType.CUSTOM)
|
||||
.withCustomCodeReference(new QCodeReference(CustomPossibleValueSource.class));
|
||||
}
|
||||
|
||||
|
||||
@ -174,6 +215,18 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the in-memory backend used in standard tests
|
||||
*******************************************************************************/
|
||||
public static QBackendMetaData defineMemoryBackend()
|
||||
{
|
||||
return new QBackendMetaData()
|
||||
.withName(MEMORY_BACKEND_NAME)
|
||||
.withBackendType(MemoryBackendModule.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the 'person' table used in standard tests.
|
||||
*******************************************************************************/
|
||||
@ -191,7 +244,32 @@ public class TestUtils
|
||||
.withField(new QFieldMetaData("lastName", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("birthDate", QFieldType.DATE))
|
||||
.withField(new QFieldMetaData("email", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("homeState", QFieldType.STRING).withPossibleValueSourceName("state"));
|
||||
.withField(new QFieldMetaData("homeStateId", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_STATE))
|
||||
.withField(new QFieldMetaData("favoriteShapeId", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_SHAPE))
|
||||
.withField(new QFieldMetaData("customValue", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_CUSTOM))
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the 'shape' table used in standard tests.
|
||||
*******************************************************************************/
|
||||
public static QTableMetaData defineTableShape()
|
||||
{
|
||||
return new QTableMetaData()
|
||||
.withName(TABLE_NAME_SHAPE)
|
||||
.withBackendName(MEMORY_BACKEND_NAME)
|
||||
.withPrimaryKeyField("id")
|
||||
.withRecordLabelFields("name")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("type", QFieldType.STRING)) // todo PVS
|
||||
.withField(new QFieldMetaData("noOfSides", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("isPolygon", QFieldType.BOOLEAN)) // mmm, should be derived from type, no?
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@ -371,6 +449,25 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void insertDefaultShapes(QInstance qInstance) throws QException
|
||||
{
|
||||
List<QRecord> shapeRecords = List.of(
|
||||
new QRecord().withTableName(TABLE_NAME_SHAPE).withValue("id", 1).withValue("name", "Triangle"),
|
||||
new QRecord().withTableName(TABLE_NAME_SHAPE).withValue("id", 2).withValue("name", "Square"),
|
||||
new QRecord().withTableName(TABLE_NAME_SHAPE).withValue("id", 3).withValue("name", "Circle"));
|
||||
|
||||
InsertInput insertInput = new InsertInput(qInstance);
|
||||
insertInput.setSession(new QSession());
|
||||
insertInput.setTableName(TABLE_NAME_SHAPE);
|
||||
insertInput.setRecords(shapeRecords);
|
||||
new InsertAction().execute(insertInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -417,4 +514,21 @@ public class TestUtils
|
||||
""");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class CustomPossibleValueSource implements QCustomPossibleValueProvider
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QPossibleValue<?> getPossibleValue(Serializable idValue)
|
||||
{
|
||||
return (new QPossibleValue<>(idValue, "Custom[" + idValue + "]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,8 +60,8 @@
|
||||
"type": "STRING",
|
||||
"possibleValueSourceName": null
|
||||
},
|
||||
"homeState": {
|
||||
"name": "homeState",
|
||||
"homeStateId": {
|
||||
"name": "homeStateId",
|
||||
"label": null,
|
||||
"backendName": null,
|
||||
"type": "STRING",
|
||||
|
@ -27,8 +27,8 @@
|
||||
"type": "DATE_TIME",
|
||||
"possibleValueSourceName": null
|
||||
},
|
||||
"homeState": {
|
||||
"name": "homeState",
|
||||
"homeStateId": {
|
||||
"name": "homeStateId",
|
||||
"backendName": null,
|
||||
"label": null,
|
||||
"type": "STRING",
|
||||
|
@ -31,11 +31,11 @@ import com.kingsrook.qqq.backend.module.filesystem.base.actions.AbstractBaseFile
|
||||
*******************************************************************************/
|
||||
public interface FilesystemBackendModuleInterface<FILE>
|
||||
{
|
||||
String CUSTOMIZER_FILE_POST_FILE_READ = "postFileRead";
|
||||
|
||||
/*******************************************************************************
|
||||
** For filesystem backends, get the module-specific action base-class, that helps
|
||||
** with functions like listing and deleting files.
|
||||
*******************************************************************************/
|
||||
AbstractBaseFilesystemAction<FILE> getActionBase();
|
||||
|
||||
}
|
||||
|
@ -31,6 +31,8 @@ import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.adapters.CsvToQRecordAdapter;
|
||||
import com.kingsrook.qqq.backend.core.adapters.JsonToQRecordAdapter;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@ -40,7 +42,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemRecordBackendDetailFields;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.AbstractFilesystemBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.AbstractFilesystemTableBackendDetails;
|
||||
@ -203,7 +204,13 @@ public abstract class AbstractBaseFilesystemAction<FILE>
|
||||
|
||||
if(queryInput.getRecordPipe() != null)
|
||||
{
|
||||
new CsvToQRecordAdapter().buildRecordsFromCsv(queryInput.getRecordPipe(), fileContents, table, null, (record -> addBackendDetailsToRecord(record, file)));
|
||||
new CsvToQRecordAdapter().buildRecordsFromCsv(queryInput.getRecordPipe(), fileContents, table, null, (record ->
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Before the records go into the pipe, make sure their backend details are added to them //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
addBackendDetailsToRecord(record, file);
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -243,6 +250,24 @@ public abstract class AbstractBaseFilesystemAction<FILE>
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public CountOutput executeCount(CountInput countInput) throws QException
|
||||
{
|
||||
QueryInput queryInput = new QueryInput(countInput.getInstance());
|
||||
queryInput.setSession(countInput.getSession());
|
||||
queryInput.setTableName(countInput.getTableName());
|
||||
queryInput.setFilter(countInput.getFilter());
|
||||
QueryOutput queryOutput = executeQuery(queryInput);
|
||||
|
||||
CountOutput countOutput = new CountOutput();
|
||||
countOutput.setCount(queryOutput.getRecords().size());
|
||||
return (countOutput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Add backend details to records about the file that they are in.
|
||||
*******************************************************************************/
|
||||
@ -281,7 +306,7 @@ public abstract class AbstractBaseFilesystemAction<FILE>
|
||||
*******************************************************************************/
|
||||
private String customizeFileContentsAfterReading(QTableMetaData table, String fileContents) throws QException
|
||||
{
|
||||
Optional<QCodeReference> optionalCustomizer = table.getCustomizer(FilesystemBackendModuleInterface.CUSTOMIZER_FILE_POST_FILE_READ);
|
||||
Optional<QCodeReference> optionalCustomizer = table.getCustomizer(FilesystemTableCustomizers.POST_READ_FILE.getRole());
|
||||
if(optionalCustomizer.isEmpty())
|
||||
{
|
||||
return (fileContents);
|
||||
|
@ -0,0 +1,72 @@
|
||||
package com.kingsrook.qqq.backend.module.filesystem.base.actions;
|
||||
|
||||
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizer;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum FilesystemTableCustomizers
|
||||
{
|
||||
POST_READ_FILE(new TableCustomizer("postReadFile", Function.class, ((Object x) ->
|
||||
{
|
||||
Function<String, String> function = (Function<String, String>) x;
|
||||
String output = function.apply(new String());
|
||||
})));
|
||||
|
||||
private final TableCustomizer tableCustomizer;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
FilesystemTableCustomizers(TableCustomizer tableCustomizer)
|
||||
{
|
||||
this.tableCustomizer = tableCustomizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Get the FilesystemTableCustomer for a given role (e.g., the role used in meta-data, not
|
||||
** the enum-constant name).
|
||||
*******************************************************************************/
|
||||
public static FilesystemTableCustomizers forRole(String name)
|
||||
{
|
||||
for(FilesystemTableCustomizers value : values())
|
||||
{
|
||||
if(value.tableCustomizer.getRole().equals(name))
|
||||
{
|
||||
return (value);
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableCustomizer
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableCustomizer getTableCustomizer()
|
||||
{
|
||||
return tableCustomizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** get the role from the tableCustomizer
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getRole()
|
||||
{
|
||||
return (tableCustomizer.getRole());
|
||||
}
|
||||
|
||||
}
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.module.filesystem.local;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
||||
@ -33,6 +34,7 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.base.actions.AbstractBaseFilesystemAction;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.local.actions.AbstractFilesystemAction;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.local.actions.FilesystemCountAction;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.local.actions.FilesystemDeleteAction;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.local.actions.FilesystemInsertAction;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.local.actions.FilesystemQueryAction;
|
||||
@ -107,6 +109,16 @@ public class FilesystemBackendModule implements QBackendModuleInterface, Filesys
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public CountInterface getCountInterface()
|
||||
{
|
||||
return new FilesystemCountAction();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.module.filesystem.local.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class FilesystemCountAction extends AbstractFilesystemAction implements CountInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public CountOutput execute(CountInput countInput) throws QException
|
||||
{
|
||||
return (executeCount(countInput));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.module.filesystem.s3.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class S3CountAction extends AbstractS3Action implements CountInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public CountOutput execute(CountInput countInput) throws QException
|
||||
{
|
||||
return (executeCount(countInput));
|
||||
}
|
||||
|
||||
}
|
@ -73,11 +73,14 @@ public class FilesystemActionTest
|
||||
TestUtils.increaseTestInstanceCounter();
|
||||
FilesystemBackendMetaData filesystemBackendMetaData = TestUtils.defineLocalFilesystemBackend();
|
||||
|
||||
File baseDirectory = new File(filesystemBackendMetaData.getBasePath());
|
||||
boolean mkdirsResult = baseDirectory.mkdirs();
|
||||
if(!mkdirsResult)
|
||||
File baseDirectory = new File(filesystemBackendMetaData.getBasePath());
|
||||
if(!baseDirectory.exists())
|
||||
{
|
||||
fail("Failed to make directories at [" + baseDirectory + "] for filesystem backend module");
|
||||
boolean mkdirsResult = baseDirectory.mkdirs();
|
||||
if(!mkdirsResult)
|
||||
{
|
||||
fail("Failed to make directories at [" + baseDirectory + "] for filesystem backend module");
|
||||
}
|
||||
}
|
||||
|
||||
writePersonJSONFiles(baseDirectory);
|
||||
@ -92,9 +95,9 @@ public class FilesystemActionTest
|
||||
private void writePersonJSONFiles(File baseDirectory) throws IOException
|
||||
{
|
||||
String fullPath = baseDirectory.getAbsolutePath();
|
||||
if (TestUtils.defineLocalFilesystemJSONPersonTable().getBackendDetails() instanceof FilesystemTableBackendDetails details)
|
||||
if(TestUtils.defineLocalFilesystemJSONPersonTable().getBackendDetails() instanceof FilesystemTableBackendDetails details)
|
||||
{
|
||||
if (StringUtils.hasContent(details.getBasePath()))
|
||||
if(StringUtils.hasContent(details.getBasePath()))
|
||||
{
|
||||
fullPath += File.separatorChar + details.getBasePath();
|
||||
}
|
||||
@ -125,9 +128,9 @@ public class FilesystemActionTest
|
||||
private void writePersonCSVFiles(File baseDirectory) throws IOException
|
||||
{
|
||||
String fullPath = baseDirectory.getAbsolutePath();
|
||||
if (TestUtils.defineLocalFilesystemCSVPersonTable().getBackendDetails() instanceof FilesystemTableBackendDetails details)
|
||||
if(TestUtils.defineLocalFilesystemCSVPersonTable().getBackendDetails() instanceof FilesystemTableBackendDetails details)
|
||||
{
|
||||
if (StringUtils.hasContent(details.getBasePath()))
|
||||
if(StringUtils.hasContent(details.getBasePath()))
|
||||
{
|
||||
fullPath += File.separatorChar + details.getBasePath();
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.module.filesystem.local.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for FilesystemCountAction
|
||||
*******************************************************************************/
|
||||
public class FilesystemCountActionTest extends FilesystemActionTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testCount1() throws QException
|
||||
{
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setInstance(TestUtils.defineInstance());
|
||||
countInput.setTableName(TestUtils.defineLocalFilesystemJSONPersonTable().getName());
|
||||
CountOutput countOutput = new FilesystemCountAction().execute(countInput);
|
||||
Assertions.assertEquals(3, countOutput.getCount(), "Unfiltered count should find all rows");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.module.filesystem.local.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class FilesystemDeleteActionTest extends FilesystemActionTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test() throws QException
|
||||
{
|
||||
assertThrows(NotImplementedException.class, () -> new FilesystemDeleteAction().execute(new DeleteInput()));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.module.filesystem.local.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class FilesystemInsertActionTest extends FilesystemActionTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test() throws QException
|
||||
{
|
||||
assertThrows(NotImplementedException.class, () -> new FilesystemInsertAction().execute(new InsertInput()));
|
||||
}
|
||||
|
||||
}
|
@ -26,14 +26,13 @@ import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemRecordBackendDetailFields;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.base.actions.FilesystemTableCustomizers;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -72,10 +71,7 @@ public class FilesystemQueryActionTest extends FilesystemActionTest
|
||||
QInstance instance = TestUtils.defineInstance();
|
||||
|
||||
QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_LOCAL_FS_JSON);
|
||||
table.withCustomizer(FilesystemBackendModuleInterface.CUSTOMIZER_FILE_POST_FILE_READ, new QCodeReference()
|
||||
.withName(ValueUpshifter.class.getName())
|
||||
.withCodeType(QCodeType.JAVA)
|
||||
.withCodeUsage(QCodeUsage.CUSTOMIZER));
|
||||
table.withCustomizer(FilesystemTableCustomizers.POST_READ_FILE.getRole(), new QCodeReference(ValueUpshifter.class, QCodeUsage.CUSTOMIZER));
|
||||
|
||||
queryInput.setInstance(instance);
|
||||
queryInput.setTableName(TestUtils.defineLocalFilesystemJSONPersonTable().getName());
|
||||
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.module.filesystem.local.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class FilesystemUpdateActionTest extends FilesystemActionTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test() throws QException
|
||||
{
|
||||
assertThrows(NotImplementedException.class, () -> new FilesystemUpdateAction().execute(new UpdateInput()));
|
||||
}
|
||||
|
||||
}
|
@ -31,8 +31,6 @@ import com.amazonaws.services.s3.model.S3ObjectSummary;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.s3.utils.S3Utils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
|
||||
@ -40,7 +38,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
** Base class for tests that want to be able to work with localstack s3.
|
||||
*******************************************************************************/
|
||||
@ExtendWith(LocalstackDockerExtension.class)
|
||||
@LocalstackDockerProperties(services = { ServiceName.S3 }, portEdge = "2960", portElasticSearch = "2961")
|
||||
@LocalstackDockerProperties(useSingleDockerContainer = true, services = { ServiceName.S3 }, portEdge = "2960", portElasticSearch = "2961")
|
||||
public class BaseS3Test
|
||||
{
|
||||
public static final String BUCKET_NAME = "localstack-test-bucket";
|
||||
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.module.filesystem.s3.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.s3.BaseS3Test;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class S3CountActionTest extends BaseS3Test
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testCount1() throws QException
|
||||
{
|
||||
CountInput countInput = initCountRequest();
|
||||
S3CountAction s3CountAction = new S3CountAction();
|
||||
s3CountAction.setS3Utils(getS3Utils());
|
||||
CountOutput countOutput = s3CountAction.execute(countInput);
|
||||
Assertions.assertEquals(5, countOutput.getCount(), "Expected # of rows from unfiltered count");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private CountInput initCountRequest() throws QException
|
||||
{
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setInstance(TestUtils.defineInstance());
|
||||
countInput.setTableName(TestUtils.defineS3CSVPersonTable().getName());
|
||||
return countInput;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.module.filesystem.s3.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.s3.BaseS3Test;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class S3DeleteActionTest extends BaseS3Test
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test() throws QException
|
||||
{
|
||||
assertThrows(NotImplementedException.class, () -> new S3DeleteAction().execute(new DeleteInput()));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.module.filesystem.s3.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.s3.BaseS3Test;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class S3InsertActionTest extends BaseS3Test
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test() throws QException
|
||||
{
|
||||
assertThrows(NotImplementedException.class, () -> new S3InsertAction().execute(new InsertInput()));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.module.filesystem.s3.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.module.filesystem.s3.BaseS3Test;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class S3UpdateActionTest extends BaseS3Test
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test() throws QException
|
||||
{
|
||||
assertThrows(NotImplementedException.class, () -> new S3UpdateAction().execute(new UpdateInput()));
|
||||
}
|
||||
|
||||
}
|
@ -200,6 +200,10 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
||||
{
|
||||
return (QueryManager.getLocalDateTime(resultSet, i));
|
||||
}
|
||||
case BOOLEAN:
|
||||
{
|
||||
return (QueryManager.getBoolean(resultSet, i));
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException("Unexpected field type: " + qFieldMetaData.getType());
|
||||
|
@ -29,6 +29,7 @@ import java.sql.Connection;
|
||||
import java.sql.Date;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Timestamp;
|
||||
@ -277,8 +278,6 @@ public class QueryManager
|
||||
*******************************************************************************/
|
||||
public static SimpleEntity executeStatementForSimpleEntity(Connection connection, String sql, Object... params) throws SQLException
|
||||
{
|
||||
throw (new NotImplementedException());
|
||||
/*
|
||||
PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params);
|
||||
statement.execute();
|
||||
ResultSet resultSet = statement.getResultSet();
|
||||
@ -290,7 +289,6 @@ public class QueryManager
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@ -355,8 +353,6 @@ public class QueryManager
|
||||
*******************************************************************************/
|
||||
public static SimpleEntity buildSimpleEntity(ResultSet resultSet) throws SQLException
|
||||
{
|
||||
throw (new NotImplementedException());
|
||||
/*
|
||||
SimpleEntity row = new SimpleEntity();
|
||||
|
||||
ResultSetMetaData metaData = resultSet.getMetaData();
|
||||
@ -365,7 +361,6 @@ public class QueryManager
|
||||
row.put(metaData.getColumnName(i), getObject(resultSet, i));
|
||||
}
|
||||
return row;
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
@ -37,7 +37,7 @@ import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
|
@ -428,12 +428,14 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
|
||||
/*******************************************************************************
|
||||
** This doesn't really test any RDBMS code, but is a checkpoint that the core
|
||||
** module is populating displayValues when it performs the system-level query action.
|
||||
** module is populating displayValues when it performs the system-level query action
|
||||
** (if so requested by input field).
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testThatDisplayValuesGetSetGoingThroughQueryAction() throws QException
|
||||
{
|
||||
QueryInput queryInput = initQueryRequest();
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
Assertions.assertEquals(5, queryOutput.getRecords().size(), "Unfiltered query should find all rows");
|
||||
|
||||
|
@ -360,4 +360,25 @@ class QueryManagerTest
|
||||
assertEquals(null, QueryManager.executeStatementForSingleValue(connection, Integer.class, "SELECT int_col FROM test_table WHERE int_col IS NULL"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testQueryForSimpleEntity() throws SQLException
|
||||
{
|
||||
Connection connection = getConnection();
|
||||
QueryManager.executeUpdate(connection, """
|
||||
INSERT INTO test_table
|
||||
( int_col, datetime_col, char_col, date_col, time_col )
|
||||
VALUES
|
||||
( 47, '2022-08-10 19:22:08', 'Q', '2022-08-10', '19:22:08')
|
||||
""");
|
||||
SimpleEntity simpleEntity = QueryManager.executeStatementForSimpleEntity(connection, "SELECT * FROM test_table");
|
||||
assertNotNull(simpleEntity);
|
||||
assertEquals(47, simpleEntity.get("INT_COL"));
|
||||
assertEquals("Q", simpleEntity.get("CHAR_COL"));
|
||||
}
|
||||
|
||||
}
|
@ -34,6 +34,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobManager;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.ProcessMetaDataAction;
|
||||
@ -109,11 +110,16 @@ public class QJavalinImplementation
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QJavalinImplementation.class);
|
||||
|
||||
private static final int SESSION_COOKIE_AGE = 60 * 60 * 24;
|
||||
private static final int SESSION_COOKIE_AGE = 60 * 60 * 24;
|
||||
private static final String SESSION_ID_COOKIE_NAME = "sessionId";
|
||||
|
||||
static QInstance qInstance;
|
||||
|
||||
private static Supplier<QInstance> qInstanceHotSwapSupplier;
|
||||
private static long lastQInstanceHotSwapMillis;
|
||||
|
||||
private static final long MILLIS_BETWEEN_HOT_SWAPS = 2500;
|
||||
|
||||
private static int DEFAULT_PORT = 8001;
|
||||
|
||||
private static Javalin service;
|
||||
@ -166,6 +172,50 @@ public class QJavalinImplementation
|
||||
// todo base path from arg? - and then potentially multiple instances too (chosen based on the root path??)
|
||||
service = Javalin.create().start(port);
|
||||
service.routes(getRoutes());
|
||||
service.before(QJavalinImplementation::hotSwapQInstance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** If there's a qInstanceHotSwapSupplier, and its been a little while, replace
|
||||
** the qInstance with a new one from the supplier. Meant to be used while doing
|
||||
** development.
|
||||
*******************************************************************************/
|
||||
public static void hotSwapQInstance(Context context)
|
||||
{
|
||||
if(qInstanceHotSwapSupplier != null)
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
if(now - lastQInstanceHotSwapMillis < MILLIS_BETWEEN_HOT_SWAPS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lastQInstanceHotSwapMillis = now;
|
||||
|
||||
try
|
||||
{
|
||||
QInstance newQInstance = qInstanceHotSwapSupplier.get();
|
||||
if(newQInstance == null)
|
||||
{
|
||||
LOG.warn("Got a null qInstance from hotSwapSupplier. Not hot-swapping.");
|
||||
return;
|
||||
}
|
||||
|
||||
new QInstanceValidator().validate(newQInstance);
|
||||
QJavalinImplementation.qInstance = newQInstance;
|
||||
LOG.info("Swapped qInstance");
|
||||
}
|
||||
catch(QInstanceValidationException e)
|
||||
{
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error swapping QInstance", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -249,7 +299,7 @@ public class QJavalinImplementation
|
||||
static void setupSession(Context context, AbstractActionInput input) throws QModuleDispatchException
|
||||
{
|
||||
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(input.getAuthenticationMetaData());
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(input.getAuthenticationMetaData());
|
||||
|
||||
try
|
||||
{
|
||||
@ -266,7 +316,7 @@ public class QJavalinImplementation
|
||||
else
|
||||
{
|
||||
String authorizationHeaderValue = context.header("Authorization");
|
||||
if (authorizationHeaderValue != null)
|
||||
if(authorizationHeaderValue != null)
|
||||
{
|
||||
String bearerPrefix = "Bearer ";
|
||||
if(authorizationHeaderValue.startsWith(bearerPrefix))
|
||||
@ -309,7 +359,7 @@ public class QJavalinImplementation
|
||||
{
|
||||
try
|
||||
{
|
||||
String table = context.pathParam("table");
|
||||
String table = context.pathParam("table");
|
||||
List<Serializable> primaryKeys = new ArrayList<>();
|
||||
primaryKeys.add(context.pathParam("primaryKey"));
|
||||
|
||||
@ -338,9 +388,9 @@ public class QJavalinImplementation
|
||||
{
|
||||
try
|
||||
{
|
||||
String table = context.pathParam("table");
|
||||
String table = context.pathParam("table");
|
||||
List<QRecord> recordList = new ArrayList<>();
|
||||
QRecord record = new QRecord();
|
||||
QRecord record = new QRecord();
|
||||
record.setTableName(table);
|
||||
recordList.add(record);
|
||||
|
||||
@ -382,9 +432,9 @@ public class QJavalinImplementation
|
||||
{
|
||||
try
|
||||
{
|
||||
String table = context.pathParam("table");
|
||||
String table = context.pathParam("table");
|
||||
List<QRecord> recordList = new ArrayList<>();
|
||||
QRecord record = new QRecord();
|
||||
QRecord record = new QRecord();
|
||||
record.setTableName(table);
|
||||
recordList.add(record);
|
||||
|
||||
@ -429,6 +479,8 @@ public class QJavalinImplementation
|
||||
|
||||
setupSession(context, queryInput);
|
||||
queryInput.setTableName(tableName);
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
|
||||
// todo - validate that the primary key is of the proper type (e.g,. not a string for an id field)
|
||||
// and throw a 400-series error (tell the user bad-request), rather than, we're doing a 500 (server error)
|
||||
@ -524,6 +576,8 @@ public class QJavalinImplementation
|
||||
QueryInput queryInput = new QueryInput(qInstance);
|
||||
setupSession(context, queryInput);
|
||||
queryInput.setTableName(context.pathParam("table"));
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.setSkip(integerQueryParam(context, "skip"));
|
||||
queryInput.setLimit(integerQueryParam(context, "limit"));
|
||||
|
||||
@ -836,4 +890,14 @@ public class QJavalinImplementation
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for qInstanceHotSwapSupplier
|
||||
*******************************************************************************/
|
||||
public static void setQInstanceHotSwapSupplier(Supplier<QInstance> qInstanceHotSwapSupplier)
|
||||
{
|
||||
QJavalinImplementation.qInstanceHotSwapSupplier = qInstanceHotSwapSupplier;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
|
@ -536,6 +536,11 @@ public class QPicoCliImplementation
|
||||
queryInput.setSession(session);
|
||||
queryInput.setTableName(tableName);
|
||||
queryInput.setSkip(subParseResult.matchedOptionValue("skip", null));
|
||||
|
||||
// todo - think about these (e.g., based on user's requested output format?
|
||||
// queryInput.setShouldGenerateDisplayValues(true);
|
||||
// queryInput.setShouldTranslatePossibleValues(true);
|
||||
|
||||
String primaryKeyValue = subParseResult.matchedPositionalValue(0, null);
|
||||
|
||||
if(primaryKeyValue == null)
|
||||
@ -581,6 +586,10 @@ public class QPicoCliImplementation
|
||||
queryInput.setLimit(subParseResult.matchedOptionValue("limit", null));
|
||||
queryInput.setFilter(generateQueryFilter(subParseResult));
|
||||
|
||||
// todo - think about these (e.g., based on user's requested output format?
|
||||
// queryInput.setShouldGenerateDisplayValues(true);
|
||||
// queryInput.setShouldTranslatePossibleValues(true);
|
||||
|
||||
QueryAction queryAction = new QueryAction();
|
||||
QueryOutput queryOutput = queryAction.execute(queryInput);
|
||||
commandLine.getOut().println(JsonUtils.toPrettyJson(queryOutput));
|
||||
|
@ -44,7 +44,7 @@ import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
|
@ -98,6 +98,11 @@
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
<version>4.10.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@ -116,6 +121,19 @@
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-maven-plugin</artifactId>
|
||||
<version>4.10.0</version>
|
||||
<configuration>
|
||||
<propertyFile>/src/main/resources/liquibase/liquibase.properties</propertyFile>
|
||||
<url>${env.LB_DB_URL}</url>
|
||||
<username>${env.LB_DB_USERNAME}</username>
|
||||
<password>${env.LB_DB_PASSWORD}</password>
|
||||
<contexts>${env.LB_CONTEXTS}</contexts>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
@ -36,7 +36,8 @@ public class SampleCli
|
||||
*******************************************************************************/
|
||||
public static void main(String[] args)
|
||||
{
|
||||
new SampleCli().run(args);
|
||||
int exitCode = new SampleCli().run(args);
|
||||
System.exit(exitCode);
|
||||
}
|
||||
|
||||
|
||||
@ -44,19 +45,19 @@ public class SampleCli
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void run(String[] args)
|
||||
int run(String[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
QInstance qInstance = SampleMetaDataProvider.defineInstance();
|
||||
QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance);
|
||||
int exitCode = qPicoCliImplementation.runCli("my-sample-cli", args);
|
||||
System.exit(exitCode);
|
||||
|
||||
return (qPicoCliImplementation.runCli("my-sample-cli", args));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
System.exit(-1);
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,6 @@
|
||||
package com.kingsrook.sampleapp;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
|
||||
import io.javalin.Javalin;
|
||||
@ -71,6 +70,24 @@ public class SampleJavalinServer
|
||||
config.enableCorsForAllOrigins();
|
||||
}).start(PORT);
|
||||
javalinService.routes(qJavalinImplementation.getRoutes());
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// set the server to hot-swap the q instance before all routes //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
QJavalinImplementation.setQInstanceHotSwapSupplier(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
return (SampleMetaDataProvider.defineInstance());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error hot-swapping meta data", e);
|
||||
return (null);
|
||||
}
|
||||
});
|
||||
javalinService.before(QJavalinImplementation::hotSwapQInstance);
|
||||
|
||||
javalinService.after(ctx ->
|
||||
ctx.res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"));
|
||||
}
|
||||
|
@ -253,7 +253,7 @@ public class SampleMetaDataProvider
|
||||
.withBackendName(RDBMS_BACKEND_NAME)
|
||||
.withPrimaryKeyField("id")
|
||||
.withRecordLabelFormat("%s %s")
|
||||
.withRecordLabelFields(List.of("firstName", "lastName"))
|
||||
.withRecordLabelFields("firstName", "lastName")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date").withIsEditable(false))
|
||||
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date").withIsEditable(false))
|
||||
@ -261,12 +261,13 @@ public class SampleMetaDataProvider
|
||||
.withField(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name").withIsRequired(true))
|
||||
.withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date"))
|
||||
.withField(new QFieldMetaData("email", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("isEmployed", QFieldType.BOOLEAN).withBackendName("is_employed"))
|
||||
.withField(new QFieldMetaData("annualSalary", QFieldType.DECIMAL).withBackendName("annual_salary").withDisplayFormat(DisplayFormat.CURRENCY))
|
||||
.withField(new QFieldMetaData("daysWorked", QFieldType.INTEGER).withBackendName("days_worked").withDisplayFormat(DisplayFormat.COMMAS))
|
||||
|
||||
.withSection(new QFieldSection("identity", "Identity", new QIcon("badge"), Tier.T1, List.of("id", "firstName", "lastName")))
|
||||
.withSection(new QFieldSection("basicInfo", "Basic Info", new QIcon("dataset"), Tier.T2, List.of("email", "birthDate")))
|
||||
.withSection(new QFieldSection("employmentInfo", "Employment Info", new QIcon("work"), Tier.T2, List.of("annualSalary", "daysWorked")))
|
||||
.withSection(new QFieldSection("employmentInfo", "Employment Info", new QIcon("work"), Tier.T2, List.of("isEmployed", "annualSalary", "daysWorked")))
|
||||
.withSection(new QFieldSection("dates", "Dates", new QIcon("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
||||
|
||||
QInstanceEnricher.setInferredFieldBackendNames(qTableMetaData);
|
||||
|
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd
|
||||
http://www.liquibase.org/xml/ns/pro
|
||||
http://www.liquibase.org/xml/ns/pro/liquibase-pro-4.1.xsd">
|
||||
|
||||
<include file="changesets/initial.xml" relativeToChangelogFile="true"/>
|
||||
|
||||
</databaseChangeLog>
|
@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd
|
||||
http://www.liquibase.org/xml/ns/pro
|
||||
http://www.liquibase.org/xml/ns/pro/liquibase-pro-4.1.xsd">
|
||||
|
||||
<changeSet author="tchamberlain" id="initial-1">
|
||||
<sql>
|
||||
DROP TABLE IF EXISTS person;
|
||||
CREATE TABLE person
|
||||
(
|
||||
id INT AUTO_INCREMENT primary key ,
|
||||
create_date TIMESTAMP DEFAULT now(),
|
||||
modify_date TIMESTAMP DEFAULT now(),
|
||||
|
||||
first_name VARCHAR(80) NOT NULL,
|
||||
last_name VARCHAR(80) NOT NULL,
|
||||
birth_date DATE,
|
||||
email VARCHAR(250) NOT NULL,
|
||||
is_employed BOOLEAN,
|
||||
annual_salary DECIMAL(12,2),
|
||||
days_worked INTEGER
|
||||
);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com', 1, 25000, 27);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (2, 'James', 'Maes', '1980-05-15', 'jmaes@mmltholdings.com', 1, 26000, 124);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com', 0, null, 0);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com', 1, 30000, 99);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com', 1, 1000000, 232);
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS carrier;
|
||||
CREATE TABLE carrier
|
||||
(
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(80) NOT NULL,
|
||||
company_code VARCHAR(80) NOT NULL,
|
||||
service_level VARCHAR(80) NOT NULL
|
||||
);
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (1, 'UPS Ground', 'UPS', 'G');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (2, 'UPS 2Day', 'UPS', '2');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (3, 'UPS International', 'UPS', 'I');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (4, 'Fedex Ground', 'FEDEX', 'G');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (5, 'Fedex Next Day', 'UPS', '1');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (6, 'Will Call', 'WILL_CALL', 'W');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (7, 'USPS Priority', 'USPS', '1');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (8, 'USPS Super Slow', 'USPS', '4');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (9, 'USPS Super Fast', 'USPS', '0');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (10, 'DHL International', 'DHL', 'I');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (11, 'GSO', 'GSO', 'G');
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS child_table;
|
||||
CREATE TABLE child_table
|
||||
(
|
||||
id INT AUTO_INCREMENT primary key,
|
||||
name VARCHAR(80) NOT NULL
|
||||
);
|
||||
INSERT INTO child_table (id, name) VALUES (1, 'Timmy');
|
||||
INSERT INTO child_table (id, name) VALUES (2, 'Jimmy');
|
||||
INSERT INTO child_table (id, name) VALUES (3, 'Johnny');
|
||||
INSERT INTO child_table (id, name) VALUES (4, 'Gracie');
|
||||
INSERT INTO child_table (id, name) VALUES (5, 'Suzie');
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS parent_table;
|
||||
CREATE TABLE parent_table
|
||||
(
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(80) NOT NULL,
|
||||
child_id INT,
|
||||
foreign key (child_id) references child_table(id)
|
||||
);
|
||||
INSERT INTO parent_table (id, name, child_id) VALUES (1, 'Tim''s Dad', 1);
|
||||
INSERT INTO parent_table (id, name, child_id) VALUES (2, 'Tim''s Mom', 1);
|
||||
INSERT INTO parent_table (id, name, child_id) VALUES (3, 'Childless Man', null);
|
||||
INSERT INTO parent_table (id, name, child_id) VALUES (4, 'Childless Woman', null);
|
||||
INSERT INTO parent_table (id, name, child_id) VALUES (5, 'Johny''s Single Dad', 3);
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS city;
|
||||
CREATE TABLE city
|
||||
(
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(80) NOT NULL,
|
||||
state VARCHAR(2) NOT NULL
|
||||
);
|
||||
INSERT INTO city (id, name, state) VALUES (1, 'Decatur', 'IL');
|
||||
INSERT INTO city (id, name, state) VALUES (2, 'Chester', 'IL');
|
||||
INSERT INTO city (id, name, state) VALUES (3, 'St. Louis', 'MO');
|
||||
INSERT INTO city (id, name, state) VALUES (4, 'Baltimore', 'MD');
|
||||
INSERT INTO city (id, name, state) VALUES (5, 'New York', 'NY');
|
||||
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
@ -0,0 +1,6 @@
|
||||
#liquibase.properties
|
||||
classpath: /src/main/resources/liquibase/lib/mysql-connector-java-8.0.29.jar
|
||||
driver: com.mysql.cj.jdbc.Driver
|
||||
changeLogFile:/src/main/resources/liquibase/changelog.xml
|
||||
logLevel: INFO
|
||||
liquibase.hub.mode=off
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.sampleapp;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for SampleCli
|
||||
*******************************************************************************/
|
||||
class SampleCliTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testExitSuccess() throws QException
|
||||
{
|
||||
int exitCode = new SampleCli().run(new String[] { "--meta-data" });
|
||||
assertEquals(0, exitCode);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testNotExitSuccess() throws QException
|
||||
{
|
||||
int exitCode = new SampleCli().run(new String[] { "asdfasdf" });
|
||||
assertNotEquals(0, exitCode);
|
||||
}
|
||||
|
||||
}
|
@ -2,14 +2,14 @@ package com.kingsrook.sampleapp;
|
||||
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for com.kingsrook.sampleapp.SampleJavalinServer
|
||||
** Unit test for SampleJavalinServer
|
||||
*******************************************************************************/
|
||||
class SampleJavalinServerTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -43,7 +43,6 @@ import com.kingsrook.qqq.backend.module.filesystem.local.actions.FilesystemQuery
|
||||
import com.kingsrook.qqq.backend.module.filesystem.local.model.metadata.FilesystemTableBackendDetails;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||
import junit.framework.Assert;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
@ -84,7 +83,7 @@ class SampleMetaDataProviderTest
|
||||
try(Connection connection = connectionManager.getConnection(SampleMetaDataProvider.defineRdbmsBackend()))
|
||||
{
|
||||
InputStream primeTestDatabaseSqlStream = SampleMetaDataProviderTest.class.getResourceAsStream("/" + sqlFileName);
|
||||
Assert.assertNotNull(primeTestDatabaseSqlStream);
|
||||
assertNotNull(primeTestDatabaseSqlStream);
|
||||
List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream);
|
||||
lines = lines.stream().filter(line -> !line.startsWith("-- ")).toList();
|
||||
String joinedSQL = String.join("\n", lines);
|
||||
|
@ -31,15 +31,16 @@ CREATE TABLE person
|
||||
birth_date DATE,
|
||||
email VARCHAR(250) NOT NULL,
|
||||
|
||||
is_employed BOOLEAN,
|
||||
annual_salary DECIMAL(12, 2),
|
||||
days_worked INTEGER
|
||||
);
|
||||
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, annual_salary, days_worked) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com', 75003.50, 1001);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, annual_salary, days_worked) VALUES (2, 'James', 'Maes', '1980-05-15', 'jmaes@mmltholdings.com', 150000, 10100);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, annual_salary, days_worked) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com', 300000, 100100);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, annual_salary, days_worked) VALUES (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com', 950000, 75);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, annual_salary, days_worked) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com', 1500000, 1);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com', 1, 75003.50, 1001);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (2, 'James', 'Maes', '1980-05-15', 'jmaes@mmltholdings.com', 1, 150000, 10100);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com', 1, 300000, 100100);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com', 1, 950000, 75);
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com', 0, 1500000, 1);
|
||||
|
||||
DROP TABLE IF EXISTS carrier;
|
||||
CREATE TABLE carrier
|
||||
|
Reference in New Issue
Block a user