Merge branch 'feature/CTLE-422-api-for-scripts' into integration/sprint-25

# Conflicts:
#	qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java
This commit is contained in:
2023-05-01 20:13:22 -05:00
58 changed files with 4200 additions and 1569 deletions

View File

@ -278,6 +278,7 @@ public class PollingAutomationPerTableRunner implements Runnable
.withPriority(record.getValueInteger("priority"))
.withCodeReference(new QCodeReference(RunRecordScriptAutomationHandler.class))
.withValues(MapBuilder.of("scriptId", record.getValue("scriptId")))
.withIncludeRecordAssociations(true)
);
}
}
@ -392,6 +393,8 @@ public class PollingAutomationPerTableRunner implements Runnable
queryInput.setFilter(filter);
queryInput.setIncludeAssociations(action.getIncludeRecordAssociations());
return (new QueryAction().execute(queryInput).getRecords());
}

View File

@ -50,4 +50,13 @@ public interface DeleteInterface
return (false);
}
/*******************************************************************************
** Specify whether this particular module's delete action can & should fetch
** records before deleting them, e.g., for audits or "not-found-checks"
*******************************************************************************/
default boolean supportsPreFetchQuery()
{
return (true);
}
}

View File

@ -37,4 +37,14 @@ public interface UpdateInterface
**
*******************************************************************************/
UpdateOutput execute(UpdateInput updateInput) throws QException;
/*******************************************************************************
** Specify whether this particular module's update action can & should fetch
** records before updating them, e.g., for audits or "not-found-checks"
*******************************************************************************/
default boolean supportsPreFetchQuery()
{
return (true);
}
}

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.actions.reporting;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -63,7 +64,7 @@ public class BufferedRecordPipe extends RecordPipe
**
*******************************************************************************/
@Override
public void addRecord(QRecord record)
public void addRecord(QRecord record) throws QException
{
buffer.add(record);
if(buffer.size() >= bufferSize)
@ -78,7 +79,7 @@ public class BufferedRecordPipe extends RecordPipe
/*******************************************************************************
**
*******************************************************************************/
public void finalFlush()
public void finalFlush() throws QException
{
if(!buffer.isEmpty())
{

View File

@ -26,10 +26,11 @@ 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.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeConsumer;
/*******************************************************************************
@ -47,7 +48,7 @@ public class RecordPipe
private boolean isTerminated = false;
private Consumer<List<QRecord>> postRecordActions = null;
private UnsafeConsumer<List<QRecord>, QException> postRecordActions = null;
/////////////////////////////////////
// See usage below for explanation //
@ -93,7 +94,7 @@ public class RecordPipe
/*******************************************************************************
** Add a record to the pipe. Will block if the pipe is full. Will noop if pipe is terminated.
*******************************************************************************/
public void addRecord(QRecord record)
public void addRecord(QRecord record) throws QException
{
if(isTerminated)
{
@ -109,7 +110,7 @@ public class RecordPipe
// (which we'll create as a field in this class, to avoid always re-constructing) //
////////////////////////////////////////////////////////////////////////////////////
singleRecordListForPostRecordActions.add(record);
postRecordActions.accept(singleRecordListForPostRecordActions);
postRecordActions.run(singleRecordListForPostRecordActions);
record = singleRecordListForPostRecordActions.remove(0);
}
@ -152,11 +153,11 @@ public class RecordPipe
/*******************************************************************************
** Add a list of records to the pipe. Will block if the pipe is full. Will noop if pipe is terminated.
*******************************************************************************/
public void addRecords(List<QRecord> records)
public void addRecords(List<QRecord> records) throws QException
{
if(postRecordActions != null)
{
postRecordActions.accept(records);
postRecordActions.run(records);
}
//////////////////////////////////////////////////////////////////////////////////////////////////
@ -207,7 +208,7 @@ public class RecordPipe
/*******************************************************************************
**
*******************************************************************************/
public void setPostRecordActions(Consumer<List<QRecord>> postRecordActions)
public void setPostRecordActions(UnsafeConsumer<List<QRecord>, QException> postRecordActions)
{
this.postRecordActions = postRecordActions;
}

View File

@ -0,0 +1,79 @@
/*
* 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.reporting;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Subclass of BufferedRecordPipe, which ultimately sends records down to an
** original RecordPipe.
**
** Meant to be used where: someone passed in a RecordPipe (so they have a reference
** to it, and they are waiting to read from it), but the producer knows that
** it will be better to buffer the records, so they want to use a buffered pipe
** (but they still need the records to end up in the original pipe - thus -
** it gets wrapped by an object of this class).
*******************************************************************************/
public class RecordPipeBufferedWrapper extends BufferedRecordPipe
{
private RecordPipe wrappedPipe;
/*******************************************************************************
** Constructor - uses default buffer size
**
*******************************************************************************/
public RecordPipeBufferedWrapper(RecordPipe wrappedPipe)
{
this.wrappedPipe = wrappedPipe;
}
/*******************************************************************************
** Constructor - customize buffer size.
**
*******************************************************************************/
public RecordPipeBufferedWrapper(Integer bufferSize, RecordPipe wrappedPipe)
{
super(bufferSize);
this.wrappedPipe = wrappedPipe;
}
/*******************************************************************************
** when it's time to actually add records into the pipe, actually add them
** into the wrapped pipe!
*******************************************************************************/
@Override
public void addRecords(List<QRecord> records) throws QException
{
wrappedPipe.addRecords(records);
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.scripts;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
/*******************************************************************************
**
*******************************************************************************/
public interface AssociatedScriptContextPrimerInterface
{
/*******************************************************************************
**
*******************************************************************************/
void primeContext(ExecuteCodeInput executeCodeInput, ScriptRevision scriptRevision) throws QException;
}

View File

@ -25,13 +25,21 @@ package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.Log4jCodeExecutionLogger;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.ScriptExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger;
import com.kingsrook.qqq.backend.core.exceptions.QCodeException;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.scripts.AbstractRunScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
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.scripts.ScriptRevision;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
@ -49,6 +57,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
*******************************************************************************/
public class ExecuteCodeAction
{
private static final QLogger LOG = QLogger.getLogger(ExecuteCodeAction.class);
/*******************************************************************************
**
@ -68,10 +79,10 @@ public class ExecuteCodeAction
try
{
String languageExecutor = switch(codeReference.getCodeType())
{
case JAVA -> "com.kingsrook.qqq.backend.core.actions.scripts.QJavaExecutor";
case JAVA_SCRIPT -> "com.kingsrook.qqq.languages.javascript.QJavaScriptExecutor";
};
{
case JAVA -> "com.kingsrook.qqq.backend.core.actions.scripts.QJavaExecutor";
case JAVA_SCRIPT -> "com.kingsrook.qqq.languages.javascript.QJavaScriptExecutor";
};
@SuppressWarnings("unchecked")
Class<? extends QCodeExecutor> executorClass = (Class<? extends QCodeExecutor>) Class.forName(languageExecutor);
@ -108,6 +119,91 @@ public class ExecuteCodeAction
/*******************************************************************************
**
*******************************************************************************/
public static ExecuteCodeInput setupExecuteCodeInput(AbstractRunScriptInput<?> input, ScriptRevision scriptRevision)
{
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput();
executeCodeInput.setInput(new HashMap<>(Objects.requireNonNullElseGet(input.getInputValues(), HashMap::new)));
executeCodeInput.setContext(new HashMap<>());
Map<String, Serializable> context = executeCodeInput.getContext();
if(input.getOutputObject() != null)
{
context.put("output", input.getOutputObject());
}
if(input.getScriptUtils() != null)
{
context.put("scriptUtils", input.getScriptUtils());
}
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
ExecuteCodeAction.addApiUtilityToContext(context, scriptRevision);
ExecuteCodeAction.setExecutionLoggerInExecuteCodeInput(input, scriptRevision, executeCodeInput);
return (executeCodeInput);
}
/*******************************************************************************
** Try to (dynamically) load the ApiScriptUtils object from the api middleware
** module -- in case the runtime doesn't have that module deployed (e.g, not in
** the project pom).
*******************************************************************************/
public static void addApiUtilityToContext(Map<String, Serializable> context, ScriptRevision scriptRevision)
{
if(!StringUtils.hasContent(scriptRevision.getApiName()) || !StringUtils.hasContent(scriptRevision.getApiVersion()))
{
return;
}
try
{
Class<?> apiScriptUtilsClass = Class.forName("com.kingsrook.qqq.api.utils.ApiScriptUtils");
Object apiScriptUtilsObject = apiScriptUtilsClass.getConstructor(String.class, String.class).newInstance(scriptRevision.getApiName(), scriptRevision.getApiVersion());
context.put("api", (Serializable) apiScriptUtilsObject);
}
catch(ClassNotFoundException e)
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// this is the only exception we're kinda expecting here - so catch for it specifically, and just log.trace - others, warn //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
LOG.trace("Couldn't load ApiScriptUtils class - qqq-middleware-api not on the classpath?");
}
catch(Exception e)
{
LOG.warn("Error adding api utility to script context", e);
}
}
/*******************************************************************************
**
*******************************************************************************/
private static void setExecutionLoggerInExecuteCodeInput(AbstractRunScriptInput<?> input, ScriptRevision scriptRevision, ExecuteCodeInput executeCodeInput)
{
/////////////////////////////////////////////////////////////////////////////////////////////////
// let caller supply a logger, or by default use StoreScriptLogAndScriptLogLineExecutionLogger //
/////////////////////////////////////////////////////////////////////////////////////////////////
QCodeExecutionLoggerInterface executionLogger = Objects.requireNonNullElseGet(input.getLogger(), () -> new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId()));
executeCodeInput.setExecutionLogger(executionLogger);
if(executionLogger instanceof ScriptExecutionLoggerInterface scriptExecutionLoggerInterface)
{
////////////////////////////////////////////////////////////////////////////////////////////////////
// if logger is aware of scripts (as opposed to a generic CodeExecution logger), give it the ids. //
////////////////////////////////////////////////////////////////////////////////////////////////////
scriptExecutionLoggerInterface.setScriptId(scriptRevision.getScriptId());
scriptExecutionLoggerInterface.setScriptRevisionId(scriptRevision.getId());
}
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -22,15 +22,14 @@
package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.ScriptExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
@ -49,8 +48,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReference;
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.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.scripts.Script;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
@ -96,6 +93,7 @@ public class RunAdHocRecordScriptAction
QueryInput queryInput = new QueryInput();
queryInput.setTableName(input.getTableName());
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, input.getRecordPrimaryKeyList())));
queryInput.setIncludeAssociations(true);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
input.setRecordList(queryOutput.getRecords());
}
@ -112,43 +110,14 @@ public class RunAdHocRecordScriptAction
/////////////
// run it! //
/////////////
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput();
executeCodeInput.setInput(new HashMap<>(Objects.requireNonNullElseGet(input.getInputValues(), HashMap::new)));
executeCodeInput.getInput().put("records", new ArrayList<>(input.getRecordList()));
executeCodeInput.setContext(new HashMap<>());
if(input.getOutputObject() != null)
{
executeCodeInput.getContext().put("output", input.getOutputObject());
}
if(input.getScriptUtils() != null)
{
executeCodeInput.getContext().put("scriptUtils", input.getScriptUtils());
}
executeCodeInput.getContext().put("api", new ScriptApi());
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
/////////////////////////////////////////////////////////////////////////////////////////////////
// let caller supply a logger, or by default use StoreScriptLogAndScriptLogLineExecutionLogger //
/////////////////////////////////////////////////////////////////////////////////////////////////
QCodeExecutionLoggerInterface executionLogger = Objects.requireNonNullElseGet(input.getLogger(), () -> new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId()));
executeCodeInput.setExecutionLogger(executionLogger);
if(executionLogger instanceof ScriptExecutionLoggerInterface scriptExecutionLoggerInterface)
{
////////////////////////////////////////////////////////////////////////////////////////////////////
// if logger is aware of scripts (as opposed to a generic CodeExecution logger), give it the ids. //
////////////////////////////////////////////////////////////////////////////////////////////////////
scriptExecutionLoggerInterface.setScriptId(scriptRevision.getScriptId());
scriptExecutionLoggerInterface.setScriptRevisionId(scriptRevision.getId());
}
ExecuteCodeInput executeCodeInput = ExecuteCodeAction.setupExecuteCodeInput(input, scriptRevision);
executeCodeInput.getInput().put("records", getRecordsForScript(input, scriptRevision));
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
output.setOutput(executeCodeOutput.getOutput());
output.setLogger(executionLogger);
output.setLogger(executeCodeInput.getExecutionLogger());
}
catch(Exception e)
{
@ -158,6 +127,37 @@ public class RunAdHocRecordScriptAction
/*******************************************************************************
**
*******************************************************************************/
private static ArrayList<? extends Serializable> getRecordsForScript(RunAdHocRecordScriptInput input, ScriptRevision scriptRevision)
{
try
{
Class<?> apiScriptUtilsClass = Class.forName("com.kingsrook.qqq.api.utils.ApiScriptUtils");
Method qRecordListToApiRecordList = apiScriptUtilsClass.getMethod("qRecordListToApiRecordList", List.class, String.class, String.class, String.class);
Object apiRecordList = qRecordListToApiRecordList.invoke(null, input.getRecordList(), input.getTableName(), scriptRevision.getApiName(), scriptRevision.getApiVersion());
// noinspection unchecked
return (ArrayList<? extends Serializable>) apiRecordList;
}
catch(ClassNotFoundException e)
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// this is the only exception we're kinda expecting here - so catch for it specifically, and just log.trace - others, warn //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
LOG.trace("Couldn't load ApiScriptUtils class - qqq-middleware-api not on the classpath?");
}
catch(Exception e)
{
LOG.warn("Error converting QRecord list to api record list", e);
}
return (new ArrayList<>(input.getRecordList()));
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -25,11 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.ScriptExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
@ -40,8 +36,6 @@ import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptO
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeReference;
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.scripts.Script;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
@ -54,6 +48,7 @@ public class RunAssociatedScriptAction
private Map<AssociatedScriptCodeReference, ScriptRevision> scriptRevisionCache = new HashMap<>();
/*******************************************************************************
**
*******************************************************************************/
@ -61,35 +56,12 @@ public class RunAssociatedScriptAction
{
ActionHelper.validateSession(input);
ScriptRevision scriptRevision = getScriptRevision(input);
ScriptRevision scriptRevision = getScriptRevision(input);
ExecuteCodeInput executeCodeInput = ExecuteCodeAction.setupExecuteCodeInput(input, scriptRevision);
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput();
executeCodeInput.setInput(new HashMap<>(input.getInputValues()));
executeCodeInput.setContext(new HashMap<>());
if(input.getOutputObject() != null)
if(input.getAssociatedScriptContextPrimerInterface() != null)
{
executeCodeInput.getContext().put("output", input.getOutputObject());
}
if(input.getScriptUtils() != null)
{
executeCodeInput.getContext().put("scriptUtils", input.getScriptUtils());
}
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
/////////////////////////////////////////////////////////////////////////////////////////////////
// let caller supply a logger, or by default use StoreScriptLogAndScriptLogLineExecutionLogger //
/////////////////////////////////////////////////////////////////////////////////////////////////
QCodeExecutionLoggerInterface executionLogger = Objects.requireNonNullElseGet(input.getLogger(), () -> new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId()));
executeCodeInput.setExecutionLogger(executionLogger);
if(executionLogger instanceof ScriptExecutionLoggerInterface scriptExecutionLoggerInterface)
{
////////////////////////////////////////////////////////////////////////////////////////////////////
// if logger is aware of scripts (as opposed to a generic CodeExecution logger), give it the ids. //
////////////////////////////////////////////////////////////////////////////////////////////////////
scriptExecutionLoggerInterface.setScriptId(scriptRevision.getScriptId());
scriptExecutionLoggerInterface.setScriptRevisionId(scriptRevision.getId());
input.getAssociatedScriptContextPrimerInterface().primeContext(executeCodeInput, scriptRevision);
}
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();

View File

@ -47,6 +47,11 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Object made available to scripts for access to qqq api (e.g., query, insert,
** etc, plus object constructors).
**
** Before scripts knew about the API, this class made sense and was used.
** But, now that scripts do know about the API, it feels like this class could
** be deleted... but, what about, a QQQ deployment without the API module...
** In that case, we might still want this class... think about it.
*******************************************************************************/
public class ScriptApi implements Serializable
{

View File

@ -184,6 +184,8 @@ public class StoreAssociatedScriptAction
QRecord scriptRevision = new QRecord()
.withValue("scriptId", script.getValue("id"))
.withValue("contents", input.getCode())
.withValue("apiName", input.getApiName())
.withValue("apiVersion", input.getApiVersion())
.withValue("commitMessage", commitMessage)
.withValue("sequenceNo", nextSequenceNo);

View File

@ -32,6 +32,7 @@ import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
/*******************************************************************************
@ -47,7 +48,7 @@ public interface TestScriptActionInterface
** Note - such a method may want or need to put an "output" object into the
** executeCodeInput's context map.
*******************************************************************************/
void setupTestScriptInput(TestScriptInput testScriptInput, ExecuteCodeInput executeCodeInput);
void setupTestScriptInput(TestScriptInput testScriptInput, ExecuteCodeInput executeCodeInput) throws QException;
/*******************************************************************************
@ -87,12 +88,21 @@ public interface TestScriptActionInterface
BuildScriptLogAndScriptLogLineExecutionLogger executionLogger = new BuildScriptLogAndScriptLogLineExecutionLogger(null, null);
executeCodeInput.setExecutionLogger(executionLogger);
setupTestScriptInput(input, executeCodeInput);
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
try
{
setupTestScriptInput(input, executeCodeInput);
ScriptRevision scriptRevision = new ScriptRevision().withApiName(input.getApiName()).withApiVersion(input.getApiVersion());
if(this instanceof AssociatedScriptContextPrimerInterface associatedScriptContextPrimerInterface)
{
associatedScriptContextPrimerInterface.primeContext(executeCodeInput, scriptRevision);
}
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
ExecuteCodeAction.addApiUtilityToContext(executeCodeInput.getContext(), scriptRevision);
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
output.setOutputObject(processTestScriptOutput(executeCodeOutput));
}

View File

@ -76,13 +76,13 @@ public class DeleteAction
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(deleteInput.getBackend());
DeleteInterface deleteInterface = qModule.getDeleteInterface();
if(CollectionUtils.nullSafeHasContents(deleteInput.getPrimaryKeys()) && deleteInput.getQueryFilter() != null)
{
throw (new QException("A delete request may not contain both a list of primary keys and a query filter."));
}
DeleteInterface deleteInterface = qModule.getDeleteInterface();
if(deleteInput.getQueryFilter() != null && !deleteInterface.supportsQueryFilterInput())
{
LOG.info("Querying for primary keys, for backend module " + qModule.getBackendType() + " which does not support queryFilter input for deletes");
@ -99,8 +99,8 @@ public class DeleteAction
}
}
List<QRecord> recordListForAudit = getRecordListForAuditIfNeeded(deleteInput);
List<QRecord> recordsWithValidationErrors = validateRecordsExistAndCanBeAccessed(deleteInput, recordListForAudit);
List<QRecord> recordListForAudit = deleteInterface.supportsPreFetchQuery() ? getRecordListForAuditIfNeeded(deleteInput) : new ArrayList<>();
List<QRecord> recordsWithValidationErrors = deleteInterface.supportsPreFetchQuery() ? validateRecordsExistAndCanBeAccessed(deleteInput, recordListForAudit) : new ArrayList<>();
DeleteOutput deleteOutput = deleteInterface.execute(deleteInput);

View File

@ -35,6 +35,7 @@ import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostQueryCusto
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.reporting.BufferedRecordPipe;
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipeBufferedWrapper;
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
import com.kingsrook.qqq.backend.core.context.QContext;
@ -85,14 +86,15 @@ public class QueryAction
if(queryInput.getRecordPipe() != null)
{
queryInput.getRecordPipe().setPostRecordActions(this::postRecordActions);
}
if(queryInput.getIncludeAssociations() && queryInput.getRecordPipe() != null)
{
//////////////////////////////////////////////
// todo - support this in the future maybe? //
//////////////////////////////////////////////
throw (new QException("Associations may not be fetched into a RecordPipe."));
if(queryInput.getIncludeAssociations())
{
//////////////////////////////////////////////////////////////////////////////////////////
// if the user requested to include associations, it's important that that is buffered, //
// (for performance reasons), so, wrap the user's pipe with a buffer //
//////////////////////////////////////////////////////////////////////////////////////////
queryInput.setRecordPipe(new RecordPipeBufferedWrapper(queryInput.getRecordPipe()));
}
}
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
@ -111,11 +113,6 @@ public class QueryAction
postRecordActions(queryOutput.getRecords());
}
if(queryInput.getIncludeAssociations())
{
manageAssociations(queryInput, queryOutput);
}
return queryOutput;
}
@ -124,8 +121,9 @@ public class QueryAction
/*******************************************************************************
**
*******************************************************************************/
private void manageAssociations(QueryInput queryInput, QueryOutput queryOutput) throws QException
private void manageAssociations(QueryInput queryInput, List<QRecord> queryOutputRecords) throws QException
{
LOG.info("In manageAssociations for " + queryInput.getTableName() + " with " + queryOutputRecords.size() + " records");
QTableMetaData table = queryInput.getTable();
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
{
@ -149,7 +147,7 @@ public class QueryAction
{
JoinOn joinOn = join.getJoinOns().get(0);
Set<Serializable> values = new HashSet<>();
for(QRecord record : queryOutput.getRecords())
for(QRecord record : queryOutputRecords)
{
Serializable value = record.getValue(joinOn.getLeftField());
values.add(value);
@ -161,7 +159,7 @@ public class QueryAction
{
filter.setBooleanOperator(QQueryFilter.BooleanOperator.OR);
for(QRecord record : queryOutput.getRecords())
for(QRecord record : queryOutputRecords)
{
QQueryFilter subFilter = new QQueryFilter();
filter.addSubFilter(subFilter);
@ -229,7 +227,7 @@ public class QueryAction
** 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)
public void postRecordActions(List<QRecord> records) throws QException
{
if(this.postQueryRecordCustomizer.isPresent())
{
@ -250,6 +248,11 @@ public class QueryAction
QValueFormatter.setDisplayValuesInRecords(queryInput.getTable(), records);
}
if(queryInput.getIncludeAssociations())
{
manageAssociations(queryInput, records);
}
//////////////////////////////
// mask any password fields //
//////////////////////////////

View File

@ -33,6 +33,7 @@ import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
import com.kingsrook.qqq.backend.core.actions.interfaces.UpdateInterface;
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
import com.kingsrook.qqq.backend.core.context.QContext;
@ -87,18 +88,24 @@ public class UpdateAction
ValueBehaviorApplier.applyFieldBehaviors(updateInput.getInstance(), updateInput.getTable(), updateInput.getRecords());
// todo - need to handle records with errors coming out of here...
List<QRecord> oldRecordList = getOldRecordListForAuditIfNeeded(updateInput);
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(updateInput.getBackend());
UpdateInterface updateInterface = qModule.getUpdateInterface();
List<QRecord> oldRecordList = updateInterface.supportsPreFetchQuery() ? getOldRecordListForAuditIfNeeded(updateInput) : new ArrayList<>();
validatePrimaryKeysAreGiven(updateInput);
validateRecordsExistAndCanBeAccessed(updateInput, oldRecordList);
if(updateInterface.supportsPreFetchQuery())
{
validateRecordsExistAndCanBeAccessed(updateInput, oldRecordList);
}
validateRequiredFields(updateInput);
ValidateRecordSecurityLockHelper.validateSecurityFields(updateInput.getTable(), updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE);
// todo pre-customization - just get to modify the request?
UpdateOutput updateOutput = qModule.getUpdateInterface().execute(updateInput);
UpdateOutput updateOutput = updateInterface.execute(updateInput);
// todo post-customization - can do whatever w/ the result if you want
List<String> errors = updateOutput.getRecords().stream().flatMap(r -> r.getErrors().stream()).toList();

View File

@ -261,7 +261,7 @@ public class ValidateRecordSecurityLockHelper
QSecurityKeyType securityKeyType = QContext.getQInstance().getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && QContext.getQSession().hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
{
LOG.debug("Session has " + securityKeyType.getAllAccessKeyName() + " - not checking this lock.");
LOG.trace("Session has " + securityKeyType.getAllAccessKeyName() + " - not checking this lock.");
}
else
{

View File

@ -31,6 +31,7 @@ import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
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.shared.mapping.AbstractQFieldMapping;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
@ -61,7 +62,7 @@ public class CsvToQRecordAdapter
** using a given mapping.
**
*******************************************************************************/
public void buildRecordsFromCsv(RecordPipe recordPipe, String csv, QTableMetaData table, AbstractQFieldMapping<?> mapping, Consumer<QRecord> recordCustomizer)
public void buildRecordsFromCsv(RecordPipe recordPipe, String csv, QTableMetaData table, AbstractQFieldMapping<?> mapping, Consumer<QRecord> recordCustomizer) throws QException
{
buildRecordsFromCsv(new InputWrapper().withRecordPipe(recordPipe).withCsv(csv).withTable(table).withMapping(mapping).withRecordCustomizer(recordCustomizer));
}
@ -73,7 +74,7 @@ public class CsvToQRecordAdapter
** using a given mapping.
**
*******************************************************************************/
public List<QRecord> buildRecordsFromCsv(String csv, QTableMetaData table, AbstractQFieldMapping<?> mapping)
public List<QRecord> buildRecordsFromCsv(String csv, QTableMetaData table, AbstractQFieldMapping<?> mapping) throws QException
{
buildRecordsFromCsv(new InputWrapper().withCsv(csv).withTable(table).withMapping(mapping));
return (recordList);
@ -87,7 +88,7 @@ public class CsvToQRecordAdapter
**
** todo - meta-data validation, type handling
*******************************************************************************/
public void buildRecordsFromCsv(InputWrapper inputWrapper)
public void buildRecordsFromCsv(InputWrapper inputWrapper) throws QException
{
String csv = inputWrapper.getCsv();
AbstractQFieldMapping<?> mapping = inputWrapper.getMapping();
@ -297,7 +298,7 @@ public class CsvToQRecordAdapter
/*******************************************************************************
** Add a record - either to the pipe, or list, whichever we're building.
*******************************************************************************/
private void addRecord(QRecord record)
private void addRecord(QRecord record) throws QException
{
if(recordPipe != null)
{

View File

@ -0,0 +1,198 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.scripts;
import java.io.Serializable;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
/*******************************************************************************
** Base class for input wrappers that end up running scripts (ExecuteCodeAction)
*******************************************************************************/
public class AbstractRunScriptInput<C extends QCodeReference> extends AbstractTableActionInput
{
private C codeReference;
private Map<String, Serializable> inputValues;
private QCodeExecutionLoggerInterface logger;
private Serializable outputObject;
private Serializable scriptUtils;
/*******************************************************************************
** Getter for codeReference
*******************************************************************************/
public C getCodeReference()
{
return (this.codeReference);
}
/*******************************************************************************
** Setter for codeReference
*******************************************************************************/
public void setCodeReference(C codeReference)
{
this.codeReference = codeReference;
}
/*******************************************************************************
** Fluent setter for codeReference
*******************************************************************************/
public AbstractRunScriptInput<C> withCodeReference(C codeReference)
{
this.codeReference = codeReference;
return (this);
}
/*******************************************************************************
** Getter for inputValues
*******************************************************************************/
public Map<String, Serializable> getInputValues()
{
return (this.inputValues);
}
/*******************************************************************************
** Setter for inputValues
*******************************************************************************/
public void setInputValues(Map<String, Serializable> inputValues)
{
this.inputValues = inputValues;
}
/*******************************************************************************
** Fluent setter for inputValues
*******************************************************************************/
public AbstractRunScriptInput<C> withInputValues(Map<String, Serializable> inputValues)
{
this.inputValues = inputValues;
return (this);
}
/*******************************************************************************
** Getter for logger
*******************************************************************************/
public QCodeExecutionLoggerInterface getLogger()
{
return (this.logger);
}
/*******************************************************************************
** Setter for logger
*******************************************************************************/
public void setLogger(QCodeExecutionLoggerInterface logger)
{
this.logger = logger;
}
/*******************************************************************************
** Fluent setter for logger
*******************************************************************************/
public AbstractRunScriptInput<C> withLogger(QCodeExecutionLoggerInterface logger)
{
this.logger = logger;
return (this);
}
/*******************************************************************************
** Getter for outputObject
*******************************************************************************/
public Serializable getOutputObject()
{
return (this.outputObject);
}
/*******************************************************************************
** Setter for outputObject
*******************************************************************************/
public void setOutputObject(Serializable outputObject)
{
this.outputObject = outputObject;
}
/*******************************************************************************
** Fluent setter for outputObject
*******************************************************************************/
public AbstractRunScriptInput<C> withOutputObject(Serializable outputObject)
{
this.outputObject = outputObject;
return (this);
}
/*******************************************************************************
** Getter for scriptUtils
*******************************************************************************/
public Serializable getScriptUtils()
{
return (this.scriptUtils);
}
/*******************************************************************************
** Setter for scriptUtils
*******************************************************************************/
public void setScriptUtils(Serializable scriptUtils)
{
this.scriptUtils = scriptUtils;
}
/*******************************************************************************
** Fluent setter for scriptUtils
*******************************************************************************/
public AbstractRunScriptInput<C> withScriptUtils(Serializable scriptUtils)
{
this.scriptUtils = scriptUtils;
return (this);
}
}

View File

@ -24,9 +24,6 @@ package com.kingsrook.qqq.backend.core.model.actions.scripts;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReference;
@ -34,18 +31,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReferen
/*******************************************************************************
**
*******************************************************************************/
public class RunAdHocRecordScriptInput extends AbstractTableActionInput
public class RunAdHocRecordScriptInput extends AbstractRunScriptInput<AdHocScriptCodeReference>
{
private AdHocScriptCodeReference codeReference;
private Map<String, Serializable> inputValues;
private List<Serializable> recordPrimaryKeyList; // can either supply recordList, or recordPrimaryKeyList
private List<QRecord> recordList;
private String tableName;
private QCodeExecutionLoggerInterface logger;
private Serializable outputObject;
private Serializable scriptUtils;
private List<Serializable> recordPrimaryKeyList; // can either supply recordList, or recordPrimaryKeyList
private List<QRecord> recordList;
@ -58,189 +47,6 @@ public class RunAdHocRecordScriptInput extends AbstractTableActionInput
/*******************************************************************************
** Getter for inputValues
**
*******************************************************************************/
public Map<String, Serializable> getInputValues()
{
return inputValues;
}
/*******************************************************************************
** Setter for inputValues
**
*******************************************************************************/
public void setInputValues(Map<String, Serializable> inputValues)
{
this.inputValues = inputValues;
}
/*******************************************************************************
** Fluent setter for inputValues
**
*******************************************************************************/
public RunAdHocRecordScriptInput withInputValues(Map<String, Serializable> inputValues)
{
this.inputValues = inputValues;
return (this);
}
/*******************************************************************************
** Getter for outputObject
**
*******************************************************************************/
public Serializable getOutputObject()
{
return outputObject;
}
/*******************************************************************************
** Setter for outputObject
**
*******************************************************************************/
public void setOutputObject(Serializable outputObject)
{
this.outputObject = outputObject;
}
/*******************************************************************************
** Fluent setter for outputObject
**
*******************************************************************************/
public RunAdHocRecordScriptInput withOutputObject(Serializable outputObject)
{
this.outputObject = outputObject;
return (this);
}
/*******************************************************************************
** Getter for logger
*******************************************************************************/
public QCodeExecutionLoggerInterface getLogger()
{
return (this.logger);
}
/*******************************************************************************
** Setter for logger
*******************************************************************************/
public void setLogger(QCodeExecutionLoggerInterface logger)
{
this.logger = logger;
}
/*******************************************************************************
** Fluent setter for logger
*******************************************************************************/
public RunAdHocRecordScriptInput withLogger(QCodeExecutionLoggerInterface logger)
{
this.logger = logger;
return (this);
}
/*******************************************************************************
** Getter for scriptUtils
**
*******************************************************************************/
public Serializable getScriptUtils()
{
return scriptUtils;
}
/*******************************************************************************
** Setter for scriptUtils
**
*******************************************************************************/
public void setScriptUtils(Serializable scriptUtils)
{
this.scriptUtils = scriptUtils;
}
/*******************************************************************************
** Getter for codeReference
*******************************************************************************/
public AdHocScriptCodeReference getCodeReference()
{
return (this.codeReference);
}
/*******************************************************************************
** Setter for codeReference
*******************************************************************************/
public void setCodeReference(AdHocScriptCodeReference codeReference)
{
this.codeReference = codeReference;
}
/*******************************************************************************
** Fluent setter for codeReference
*******************************************************************************/
public RunAdHocRecordScriptInput withCodeReference(AdHocScriptCodeReference codeReference)
{
this.codeReference = codeReference;
return (this);
}
/*******************************************************************************
** Getter for tableName
*******************************************************************************/
public String getTableName()
{
return (this.tableName);
}
/*******************************************************************************
** Setter for tableName
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName
*******************************************************************************/
public RunAdHocRecordScriptInput withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
** Getter for recordList
*******************************************************************************/

View File

@ -22,187 +22,46 @@
package com.kingsrook.qqq.backend.core.model.actions.scripts;
import java.io.Serializable;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.actions.scripts.AssociatedScriptContextPrimerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeReference;
/*******************************************************************************
**
*******************************************************************************/
public class RunAssociatedScriptInput extends AbstractTableActionInput
public class RunAssociatedScriptInput extends AbstractRunScriptInput<AssociatedScriptCodeReference>
{
private AssociatedScriptCodeReference codeReference;
private Map<String, Serializable> inputValues;
private QCodeExecutionLoggerInterface logger;
private Serializable outputObject;
private Serializable scriptUtils;
private AssociatedScriptContextPrimerInterface associatedScriptContextPrimerInterface;
/*******************************************************************************
**
** Getter for associatedScriptContextPrimerInterface
*******************************************************************************/
public RunAssociatedScriptInput()
public AssociatedScriptContextPrimerInterface getAssociatedScriptContextPrimerInterface()
{
return (this.associatedScriptContextPrimerInterface);
}
/*******************************************************************************
** Getter for codeReference
**
** Setter for associatedScriptContextPrimerInterface
*******************************************************************************/
public AssociatedScriptCodeReference getCodeReference()
public void setAssociatedScriptContextPrimerInterface(AssociatedScriptContextPrimerInterface associatedScriptContextPrimerInterface)
{
return codeReference;
this.associatedScriptContextPrimerInterface = associatedScriptContextPrimerInterface;
}
/*******************************************************************************
** Setter for codeReference
**
** Fluent setter for associatedScriptContextPrimerInterface
*******************************************************************************/
public void setCodeReference(AssociatedScriptCodeReference codeReference)
public RunAssociatedScriptInput withAssociatedScriptContextPrimerInterface(AssociatedScriptContextPrimerInterface associatedScriptContextPrimerInterface)
{
this.codeReference = codeReference;
}
/*******************************************************************************
** Fluent setter for codeReference
**
*******************************************************************************/
public RunAssociatedScriptInput withCodeReference(AssociatedScriptCodeReference codeReference)
{
this.codeReference = codeReference;
this.associatedScriptContextPrimerInterface = associatedScriptContextPrimerInterface;
return (this);
}
/*******************************************************************************
** Getter for inputValues
**
*******************************************************************************/
public Map<String, Serializable> getInputValues()
{
return inputValues;
}
/*******************************************************************************
** Setter for inputValues
**
*******************************************************************************/
public void setInputValues(Map<String, Serializable> inputValues)
{
this.inputValues = inputValues;
}
/*******************************************************************************
** Fluent setter for inputValues
**
*******************************************************************************/
public RunAssociatedScriptInput withInputValues(Map<String, Serializable> inputValues)
{
this.inputValues = inputValues;
return (this);
}
/*******************************************************************************
** Getter for outputObject
**
*******************************************************************************/
public Serializable getOutputObject()
{
return outputObject;
}
/*******************************************************************************
** Setter for outputObject
**
*******************************************************************************/
public void setOutputObject(Serializable outputObject)
{
this.outputObject = outputObject;
}
/*******************************************************************************
** Fluent setter for outputObject
**
*******************************************************************************/
public RunAssociatedScriptInput withOutputObject(Serializable outputObject)
{
this.outputObject = outputObject;
return (this);
}
/*******************************************************************************
** Getter for logger
*******************************************************************************/
public QCodeExecutionLoggerInterface getLogger()
{
return (this.logger);
}
/*******************************************************************************
** Setter for logger
*******************************************************************************/
public void setLogger(QCodeExecutionLoggerInterface logger)
{
this.logger = logger;
}
/*******************************************************************************
** Fluent setter for logger
*******************************************************************************/
public RunAssociatedScriptInput withLogger(QCodeExecutionLoggerInterface logger)
{
this.logger = logger;
return (this);
}
/*******************************************************************************
** Getter for scriptUtils
**
*******************************************************************************/
public Serializable getScriptUtils()
{
return scriptUtils;
}
/*******************************************************************************
** Setter for scriptUtils
**
*******************************************************************************/
public void setScriptUtils(Serializable scriptUtils)
{
this.scriptUtils = scriptUtils;
}
}

View File

@ -35,6 +35,8 @@ public class StoreAssociatedScriptInput extends AbstractTableActionInput
private Serializable recordPrimaryKey;
private String code;
private String apiName;
private String apiVersion;
private String commitMessage;
@ -183,4 +185,66 @@ public class StoreAssociatedScriptInput extends AbstractTableActionInput
return (this);
}
/*******************************************************************************
** Getter for apiName
*******************************************************************************/
public String getApiName()
{
return (this.apiName);
}
/*******************************************************************************
** Setter for apiName
*******************************************************************************/
public void setApiName(String apiName)
{
this.apiName = apiName;
}
/*******************************************************************************
** Fluent setter for apiName
*******************************************************************************/
public StoreAssociatedScriptInput withApiName(String apiName)
{
this.apiName = apiName;
return (this);
}
/*******************************************************************************
** Getter for apiVersion
*******************************************************************************/
public String getApiVersion()
{
return (this.apiVersion);
}
/*******************************************************************************
** Setter for apiVersion
*******************************************************************************/
public void setApiVersion(String apiVersion)
{
this.apiVersion = apiVersion;
}
/*******************************************************************************
** Fluent setter for apiVersion
*******************************************************************************/
public StoreAssociatedScriptInput withApiVersion(String apiVersion)
{
this.apiVersion = apiVersion;
return (this);
}
}

View File

@ -36,6 +36,9 @@ public class TestScriptInput extends AbstractTableActionInput
private Map<String, Serializable> inputValues;
private QCodeReference codeReference;
private String apiName;
private String apiVersion;
/*******************************************************************************
@ -113,4 +116,66 @@ public class TestScriptInput extends AbstractTableActionInput
return (this);
}
/*******************************************************************************
** Getter for apiName
*******************************************************************************/
public String getApiName()
{
return (this.apiName);
}
/*******************************************************************************
** Setter for apiName
*******************************************************************************/
public void setApiName(String apiName)
{
this.apiName = apiName;
}
/*******************************************************************************
** Fluent setter for apiName
*******************************************************************************/
public TestScriptInput withApiName(String apiName)
{
this.apiName = apiName;
return (this);
}
/*******************************************************************************
** Getter for apiVersion
*******************************************************************************/
public String getApiVersion()
{
return (this.apiVersion);
}
/*******************************************************************************
** Setter for apiVersion
*******************************************************************************/
public void setApiVersion(String apiVersion)
{
this.apiVersion = apiVersion;
}
/*******************************************************************************
** Fluent setter for apiVersion
*******************************************************************************/
public TestScriptInput withApiVersion(String apiVersion)
{
this.apiVersion = apiVersion;
return (this);
}
}

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.io.Serializable;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -63,7 +64,7 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
** that could be read asynchronously, at any time, by another thread - SO - only
** completely populated records should be passed into this method.
*******************************************************************************/
public void addRecord(QRecord record)
public void addRecord(QRecord record) throws QException
{
storage.addRecord(record);
}
@ -73,7 +74,7 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
/*******************************************************************************
** add a list of records to this output
*******************************************************************************/
public void addRecords(List<QRecord> records)
public void addRecords(List<QRecord> records) throws QException
{
storage.addRecords(records);
}

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
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.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -53,7 +54,7 @@ class QueryOutputRecordPipe implements QueryOutputStorageInterface
** add a record to this output
*******************************************************************************/
@Override
public void addRecord(QRecord record)
public void addRecord(QRecord record) throws QException
{
recordPipe.addRecord(record);
}
@ -64,7 +65,7 @@ class QueryOutputRecordPipe implements QueryOutputStorageInterface
** add a list of records to this output
*******************************************************************************/
@Override
public void addRecords(List<QRecord> records)
public void addRecords(List<QRecord> records) throws QException
{
recordPipe.addRecords(records);
}

View File

@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -36,13 +37,13 @@ interface QueryOutputStorageInterface
/*******************************************************************************
** add a records to this output
*******************************************************************************/
void addRecord(QRecord record);
void addRecord(QRecord record) throws QException;
/*******************************************************************************
** add a list of records to this output
*******************************************************************************/
void addRecords(List<QRecord> records);
void addRecords(List<QRecord> records) throws QException;
/*******************************************************************************
** Get all stored records

View File

@ -38,6 +38,12 @@ public class TableAutomationAction
private Integer priority = 500;
private QQueryFilter filter;
////////////////////////////////////////////////////////////////////////
// flag that will cause the records to cause their associations to be //
// fetched, when they are looked up for passing into the action //
////////////////////////////////////////////////////////////////////////
private boolean includeRecordAssociations = false;
private Map<String, Serializable> values;
////////////////////////////////
@ -292,4 +298,35 @@ public class TableAutomationAction
return (this);
}
/*******************************************************************************
** Getter for includeRecordAssociations
*******************************************************************************/
public boolean getIncludeRecordAssociations()
{
return (this.includeRecordAssociations);
}
/*******************************************************************************
** Setter for includeRecordAssociations
*******************************************************************************/
public void setIncludeRecordAssociations(boolean includeRecordAssociations)
{
this.includeRecordAssociations = includeRecordAssociations;
}
/*******************************************************************************
** Fluent setter for includeRecordAssociations
*******************************************************************************/
public TableAutomationAction withIncludeRecordAssociations(boolean includeRecordAssociations)
{
this.includeRecordAssociations = includeRecordAssociations;
return (this);
}
}

View File

@ -27,6 +27,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
/*******************************************************************************
@ -48,16 +49,22 @@ public class ScriptRevision extends QRecordEntity
@QField(possibleValueSourceName = "script")
private Integer scriptId;
@QField(possibleValueSourceName = "apiVersion", label = "API Version")
private String apiVersion;
@QField(possibleValueSourceName = "apiName", label = "API Name")
private String apiName;
@QField()
private String contents;
@QField()
private Integer sequenceNo;
@QField()
@QField(maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS)
private String commitMessage;
@QField()
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS)
private String author;
@ -353,4 +360,66 @@ public class ScriptRevision extends QRecordEntity
return (this);
}
/*******************************************************************************
** Getter for apiVersion
*******************************************************************************/
public String getApiVersion()
{
return (this.apiVersion);
}
/*******************************************************************************
** Setter for apiVersion
*******************************************************************************/
public void setApiVersion(String apiVersion)
{
this.apiVersion = apiVersion;
}
/*******************************************************************************
** Fluent setter for apiVersion
*******************************************************************************/
public ScriptRevision withApiVersion(String apiVersion)
{
this.apiVersion = apiVersion;
return (this);
}
/*******************************************************************************
** Getter for apiName
*******************************************************************************/
public String getApiName()
{
return (this.apiName);
}
/*******************************************************************************
** Setter for apiName
*******************************************************************************/
public void setApiName(String apiName)
{
this.apiName = apiName;
}
/*******************************************************************************
** Fluent setter for apiName
*******************************************************************************/
public ScriptRevision withApiName(String apiName)
{
this.apiName = apiName;
return (this);
}
}

View File

@ -389,6 +389,23 @@ public class ScriptsMetaDataProvider
tableMetaData.getField("contents").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR).withValue(AdornmentType.CodeEditorValues.languageMode("javascript")));
tableMetaData.getField("scriptId").withFieldAdornment(AdornmentType.Size.LARGE.toAdornment());
try
{
////////////////////////////////////////////////////////////////////////////////////////////////////
// if the api module is loaded, then add a section to the table for the api name & version fields //
////////////////////////////////////////////////////////////////////////////////////////////////////
Class.forName("com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataProvider");
tableMetaData.getSections().add(1, new QFieldSection("api", "API", new QIcon().withName("code"), Tier.T2, List.of("apiName", "apiVersion")));
}
catch(ClassNotFoundException e)
{
/////////////////////////////////////////////////////////////////////////////////////////////////////
// if the api module is not loaded, then make sure we don't have these fields in our scripts table //
/////////////////////////////////////////////////////////////////////////////////////////////////////
tableMetaData.getFields().remove("apiName");
tableMetaData.getFields().remove("apiVersion");
}
return (tableMetaData);
}

View File

@ -26,6 +26,7 @@ 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;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
@ -43,7 +44,16 @@ public class MemoryQueryAction implements QueryInterface
try
{
QueryOutput queryOutput = new QueryOutput(queryInput);
queryOutput.addRecords(MemoryRecordStore.getInstance().query(queryInput));
///////////////////////////////////////////////////////////////////////////////////////////////////////
// add the records to the output one-by-one -- this more closely matches how "real" backends perform //
// and works better w/ pipes //
///////////////////////////////////////////////////////////////////////////////////////////////////////
for(QRecord qRecord : MemoryRecordStore.getInstance().query(queryInput))
{
queryOutput.addRecord(qRecord);
}
return (queryOutput);
}
catch(Exception e)

View File

@ -83,6 +83,9 @@ public class ExtractViaQueryStep extends AbstractExtractStep
queryInput.setRecordPipe(getRecordPipe());
queryInput.setLimit(getLimit());
queryInput.setAsyncJobCallback(runBackendStepInput.getAsyncJobCallback());
customizeInputPreQuery(queryInput);
new QueryAction().execute(queryInput);
///////////////////////////////////////////////////////////////////
@ -92,6 +95,16 @@ public class ExtractViaQueryStep extends AbstractExtractStep
/*******************************************************************************
** chance for sub-classes to change things about the query input, if they want.
*******************************************************************************/
protected void customizeInputPreQuery(QueryInput queryInput)
{
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -28,6 +28,7 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInpu
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.scripts.Script;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -70,4 +71,16 @@ public class RunRecordScriptExtractStep extends ExtractViaQueryStep
super.preRun(runBackendStepInput, runBackendStepOutput);
}
/*******************************************************************************
** Make sure associations are fetched (so api records have children!)
*******************************************************************************/
@Override
protected void customizeInputPreQuery(QueryInput queryInput)
{
super.customizeInputPreQuery(queryInput);
queryInput.setIncludeAssociations(true);
}
}

View File

@ -112,6 +112,8 @@ public class StoreScriptRevisionProcessStep implements BackendStep
QRecord scriptRevision = new QRecord()
.withValue("scriptId", script.getValue("id"))
.withValue("contents", input.getValueString("contents"))
.withValue("apiName", input.getValueString("apiName"))
.withValue("apiVersion", input.getValueString("apiVersion"))
.withValue("commitMessage", commitMessage)
.withValue("sequenceNo", nextSequenceNo);

View File

@ -74,11 +74,12 @@ public class TestScriptProcessStep implements BackendStep
// get inputs //
////////////////
Integer scriptId = input.getValueInteger("scriptId");
String code = input.getValueString("code");
ScriptRevision scriptRevision = new ScriptRevision();
scriptRevision.setScriptId(scriptId);
scriptRevision.setContents(code);
scriptRevision.setContents(input.getValueString("code"));
scriptRevision.setApiName(input.getValueString("apiName"));
scriptRevision.setApiVersion(input.getValueString("apiVersion"));
BuildScriptLogAndScriptLogLineExecutionLogger executionLogger = new BuildScriptLogAndScriptLogLineExecutionLogger(null, null);
@ -106,6 +107,7 @@ public class TestScriptProcessStep implements BackendStep
QueryInput queryInput = new QueryInput();
queryInput.setTableName(tableName);
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, recordPrimaryKeyList.split(","))));
queryInput.setIncludeAssociations(true);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
if(CollectionUtils.nullSafeIsEmpty(queryOutput.getRecords()))
{