diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/ActionHelper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/ActionHelper.java
index 11ef0eeb..b1662c6a 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/ActionHelper.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/ActionHelper.java
@@ -30,13 +30,10 @@ import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModu
/*******************************************************************************
- **
+ ** Utility methods to be shared by all of the various Actions (e.g., InsertAction)
*******************************************************************************/
public class ActionHelper
{
- private int f;
-
-
/*******************************************************************************
**
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/QBackendTransaction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/QBackendTransaction.java
index 4220ec90..64a0c57e 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/QBackendTransaction.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/QBackendTransaction.java
@@ -22,7 +22,6 @@
package com.kingsrook.qqq.backend.core.actions;
-import java.io.IOException;
import com.kingsrook.qqq.backend.core.exceptions.QException;
@@ -31,12 +30,14 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
** part of a transaction.
**
** Most obvious use-case would be a JDBC Connection. See subclass in rdbms module.
+ **
+ ** Note: One would imagine that this class shouldn't ever implement Serializable...
*******************************************************************************/
public class QBackendTransaction
{
/*******************************************************************************
- **
+ ** Commit the transaction.
*******************************************************************************/
public void commit() throws QException
{
@@ -48,7 +49,7 @@ public class QBackendTransaction
/*******************************************************************************
- **
+ ** Rollback the transaction.
*******************************************************************************/
public void rollback() throws QException
{
@@ -60,18 +61,8 @@ public class QBackendTransaction
/*******************************************************************************
- * Closes this stream and releases any system resources associated
- * with it. If the stream is already closed then invoking this
- * method has no effect.
- *
- *
As noted in {@link AutoCloseable#close()}, cases where the
- * close may fail require careful attention. It is strongly advised
- * to relinquish the underlying resources and to internally
- * mark the {@code Closeable} as closed, prior to throwing
- * the {@code IOException}.
- *
- * @throws IOException
- * if an I/O error occurs
+ ** Close any resources associated with the transaction. In theory, should only
+ ** be called after a commit or rollback was done.
*******************************************************************************/
public void close()
{
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java
index f15e319c..ce21b93f 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java
@@ -30,6 +30,7 @@ import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
+import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
@@ -55,7 +56,7 @@ public class MetaDataAction
// todo pre-customization - just get to modify the request?
MetaDataOutput metaDataOutput = new MetaDataOutput();
- Map treeNodes = new LinkedHashMap<>();
+ Map treeNodes = new LinkedHashMap<>();
/////////////////////////////////////
// map tables to frontend metadata //
@@ -64,7 +65,7 @@ public class MetaDataAction
for(Map.Entry entry : metaDataInput.getInstance().getTables().entrySet())
{
tables.put(entry.getKey(), new QFrontendTableMetaData(entry.getValue(), false));
- treeNodes.put(entry.getKey(), new QFrontendAppMetaData(entry.getValue()));
+ treeNodes.put(entry.getKey(), new AppTreeNode(entry.getValue()));
}
metaDataOutput.setTables(tables);
@@ -75,7 +76,7 @@ public class MetaDataAction
for(Map.Entry entry : metaDataInput.getInstance().getProcesses().entrySet())
{
processes.put(entry.getKey(), new QFrontendProcessMetaData(entry.getValue(), false));
- treeNodes.put(entry.getKey(), new QFrontendAppMetaData(entry.getValue()));
+ treeNodes.put(entry.getKey(), new AppTreeNode(entry.getValue()));
}
metaDataOutput.setProcesses(processes);
@@ -86,11 +87,11 @@ public class MetaDataAction
for(Map.Entry entry : metaDataInput.getInstance().getApps().entrySet())
{
apps.put(entry.getKey(), new QFrontendAppMetaData(entry.getValue()));
- treeNodes.put(entry.getKey(), new QFrontendAppMetaData(entry.getValue()));
+ treeNodes.put(entry.getKey(), new AppTreeNode(entry.getValue()));
for(QAppChildMetaData child : entry.getValue().getChildren())
{
- apps.get(entry.getKey()).addChild(new QFrontendAppMetaData(child));
+ apps.get(entry.getKey()).addChild(new AppTreeNode(child));
}
}
metaDataOutput.setApps(apps);
@@ -98,7 +99,7 @@ public class MetaDataAction
////////////////////////////////////////////////
// organize app tree nodes by their hierarchy //
////////////////////////////////////////////////
- List appTree = new ArrayList<>();
+ List appTree = new ArrayList<>();
for(QAppMetaData appMetaData : metaDataInput.getInstance().getApps().values())
{
if(appMetaData.getParentAppName() == null)
@@ -118,9 +119,9 @@ public class MetaDataAction
/*******************************************************************************
**
*******************************************************************************/
- private void buildAppTree(Map treeNodes, List nodeList, QAppChildMetaData childMetaData)
+ private void buildAppTree(Map treeNodes, List nodeList, QAppChildMetaData childMetaData)
{
- QFrontendAppMetaData treeNode = treeNodes.get(childMetaData.getName());
+ AppTreeNode treeNode = treeNodes.get(childMetaData.getName());
if(treeNode == null)
{
return;
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java
index c09ce3a9..eec134f0 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java
@@ -50,7 +50,10 @@ public class QueryAction
QueryOutput queryOutput = qModule.getQueryInterface().execute(queryInput);
// todo post-customization - can do whatever w/ the result if you want
- QValueFormatter.setDisplayValuesInRecords(queryInput.getTable(), queryOutput.getRecords());
+ if (queryInput.getRecordPipe() == null)
+ {
+ QValueFormatter.setDisplayValuesInRecords(queryInput.getTable(), queryOutput.getRecords());
+ }
return queryOutput;
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java
index 278358fa..6104f9db 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java
@@ -447,7 +447,6 @@ public class QInstanceEnricher
/*******************************************************************************
-<<<<<<< HEAD
** for all fields in a table, set their backendName, using the default "inference" logic
** see {@link #inferBackendName(String)}
*******************************************************************************/
@@ -482,6 +481,17 @@ public class QInstanceEnricher
/*******************************************************************************
** Do a default mapping from a camelCase field name to an underscore_style
** name for a backend.
+ **
+ ** Examples:
+ **
+ ** - wordAnotherWordMoreWords -> word_another_word_more_words
+ ** - lUlUlUl -> l_ul_ul_ul
+ ** - StartsUpper -> starts_upper
+ ** - TLAFirst -> tla_first
+ ** - wordThenTLAInMiddle -> word_then_tla_in_middle
+ ** - endWithTLA -> end_with_tla
+ ** - TLAAndAnotherTLA -> tla_and_another_tla
+ **
*******************************************************************************/
static String inferBackendName(String fieldName)
{
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/metadata/MetaDataOutput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/metadata/MetaDataOutput.java
index f47cc424..759ba708 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/metadata/MetaDataOutput.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/metadata/MetaDataOutput.java
@@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.actions.metadata;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
+import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
@@ -40,7 +41,7 @@ public class MetaDataOutput extends AbstractActionOutput
private Map processes;
private Map apps;
- private List appTree;
+ private List appTree;
@@ -93,7 +94,7 @@ public class MetaDataOutput extends AbstractActionOutput
** Getter for appTree
**
*******************************************************************************/
- public List getAppTree()
+ public List getAppTree()
{
return appTree;
}
@@ -104,7 +105,7 @@ public class MetaDataOutput extends AbstractActionOutput
** Setter for appTree
**
*******************************************************************************/
- public void setAppTree(List appTree)
+ public void setAppTree(List appTree)
{
this.appTree = appTree;
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QField.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QField.java
index cbdb9616..6fd3eeb6 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QField.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QField.java
@@ -26,17 +26,22 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
-import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
/*******************************************************************************
+ ** Annotation to place onto fields in a QRecordEntity, to add additional attributes
+ ** for propagating down into the corresponding QFieldMetaData
**
*******************************************************************************/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface QField
{
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ String label() default "";
+
/*******************************************************************************
**
*******************************************************************************/
@@ -51,4 +56,13 @@ public @interface QField
**
*******************************************************************************/
boolean isEditable() default true;
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ String displayFormat() default "";
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ // new attributes here likely need implementation in QFieldMetaData.constructFromGetter //
+ //////////////////////////////////////////////////////////////////////////////////////////
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java
index 066d2aca..8967c579 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java
@@ -28,7 +28,6 @@ import java.util.Optional;
import com.github.hervian.reflection.Fun;
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.utils.StringUtils;
@@ -120,10 +119,20 @@ public class QFieldMetaData
setIsRequired(fieldAnnotation.isRequired());
setIsEditable(fieldAnnotation.isEditable());
+ if(StringUtils.hasContent(fieldAnnotation.label()))
+ {
+ setLabel(fieldAnnotation.label());
+ }
+
if(StringUtils.hasContent(fieldAnnotation.backendName()))
{
setBackendName(fieldAnnotation.backendName());
}
+
+ if(StringUtils.hasContent(fieldAnnotation.displayFormat()))
+ {
+ setDisplayFormat(fieldAnnotation.displayFormat());
+ }
}
}
catch(QException qe)
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/AppTreeNode.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/AppTreeNode.java
new file mode 100644
index 00000000..5b912094
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/AppTreeNode.java
@@ -0,0 +1,152 @@
+/*
+ * 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 .
+ */
+
+package com.kingsrook.qqq.backend.core.model.metadata.frontend;
+
+
+import java.util.ArrayList;
+import java.util.List;
+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.QProcessMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+
+
+/*******************************************************************************
+ ** Frontend-version of objects that are parts of the app-hierarchy/tree.
+ ** e.g., Tables, Processes, and Apps themselves (since they can be nested).
+ **
+ ** These objects are organized into a tree - where each Node can have 0 or more
+ ** other Nodes as children.
+ *******************************************************************************/
+public class AppTreeNode
+{
+ private AppTreeNodeType type;
+ private String name;
+ private String label;
+ private List children;
+
+ private String iconName;
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public AppTreeNode(QAppChildMetaData appChildMetaData)
+ {
+ this.name = appChildMetaData.getName();
+ this.label = appChildMetaData.getLabel();
+
+ if(appChildMetaData.getClass().equals(QTableMetaData.class))
+ {
+ this.type = AppTreeNodeType.TABLE;
+ }
+ else if(appChildMetaData.getClass().equals(QProcessMetaData.class))
+ {
+ this.type = AppTreeNodeType.PROCESS;
+ }
+ else if(appChildMetaData.getClass().equals(QAppMetaData.class))
+ {
+ this.type = AppTreeNodeType.APP;
+ children = new ArrayList<>();
+ }
+ else
+ {
+ throw (new IllegalStateException("Unrecognized class for app child meta data: " + appChildMetaData.getClass()));
+ }
+
+ if(appChildMetaData.getIcon() != null)
+ {
+ // todo - propagate icons from parents, if they aren't set here...
+ this.iconName = appChildMetaData.getIcon().getName();
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for type
+ **
+ *******************************************************************************/
+ public AppTreeNodeType getType()
+ {
+ return type;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for name
+ **
+ *******************************************************************************/
+ public String getName()
+ {
+ return name;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for label
+ **
+ *******************************************************************************/
+ public String getLabel()
+ {
+ return label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for children
+ **
+ *******************************************************************************/
+ public List getChildren()
+ {
+ return children;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for iconName
+ **
+ *******************************************************************************/
+ public String getIconName()
+ {
+ return iconName;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void addChild(AppTreeNode childTreeNode)
+ {
+ if(children == null)
+ {
+ children = new ArrayList<>();
+ }
+ children.add(childTreeNode);
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/AppTreeNodeType.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/AppTreeNodeType.java
index 09c954f4..3b2c7eaf 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/AppTreeNodeType.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/AppTreeNodeType.java
@@ -23,7 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend;
/*******************************************************************************
- **
+ ** Type for an Node in the an app tree.
*******************************************************************************/
public enum AppTreeNodeType
{
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendAppMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendAppMetaData.java
index 85d9d885..6f792f97 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendAppMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendAppMetaData.java
@@ -27,10 +27,6 @@ import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
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.QProcessMetaData;
-import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
-import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
@@ -40,12 +36,10 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
@JsonInclude(Include.NON_NULL)
public class QFrontendAppMetaData
{
- private AppTreeNodeType type;
-
private String name;
private String label;
- private List children = new ArrayList<>();
+ private List children = new ArrayList<>();
private String iconName;
@@ -59,24 +53,7 @@ public class QFrontendAppMetaData
this.name = appChildMetaData.getName();
this.label = appChildMetaData.getLabel();
- if(appChildMetaData.getClass().equals(QTableMetaData.class))
- {
- this.type = AppTreeNodeType.TABLE;
- }
- else if(appChildMetaData.getClass().equals(QProcessMetaData.class))
- {
- this.type = AppTreeNodeType.PROCESS;
- }
- else if(appChildMetaData.getClass().equals(QAppMetaData.class))
- {
- this.type = AppTreeNodeType.APP;
- }
- else
- {
- throw (new IllegalStateException("Unrecognized class for app child meta data: " + appChildMetaData.getClass()));
- }
-
- if(appChildMetaData.getIcon() != null && StringUtils.hasContent(appChildMetaData.getIcon().getName()))
+ if(appChildMetaData.getIcon() != null)
{
this.iconName = appChildMetaData.getIcon().getName();
}
@@ -106,22 +83,11 @@ public class QFrontendAppMetaData
- /*******************************************************************************
- ** Getter for type
- **
- *******************************************************************************/
- public AppTreeNodeType getType()
- {
- return type;
- }
-
-
-
/*******************************************************************************
** Getter for children
**
*******************************************************************************/
- public List getChildren()
+ public List getChildren()
{
return children;
}
@@ -153,12 +119,12 @@ public class QFrontendAppMetaData
/*******************************************************************************
**
*******************************************************************************/
- public void addChild(QFrontendAppMetaData qFrontendAppMetaData)
+ public void addChild(AppTreeNode childAppTreeNode)
{
if(children == null)
{
children = new ArrayList<>();
}
- children.add(qFrontendAppMetaData);
+ children.add(childAppTreeNode);
}
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendProcessMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendProcessMetaData.java
index f6cb8e0a..182d1b9b 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendProcessMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendProcessMetaData.java
@@ -30,7 +30,6 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
-import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
@@ -81,7 +80,7 @@ public class QFrontendProcessMetaData
}
}
- if(processMetaData.getIcon() != null && StringUtils.hasContent(processMetaData.getIcon().getName()))
+ if(processMetaData.getIcon() != null)
{
this.iconName = processMetaData.getIcon().getName();
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendTableMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendTableMetaData.java
index 283402af..3ad7774e 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendTableMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendTableMetaData.java
@@ -30,7 +30,6 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
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.utils.StringUtils;
/*******************************************************************************
@@ -78,7 +77,7 @@ public class QFrontendTableMetaData
this.sections = tableMetaData.getSections();
}
- if(tableMetaData.getIcon() != null && StringUtils.hasContent(tableMetaData.getIcon().getName()))
+ if(tableMetaData.getIcon() != null)
{
this.iconName = tableMetaData.getIcon().getName();
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppChildMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppChildMetaData.java
index 85db26ef..088aefc0 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppChildMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppChildMetaData.java
@@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
/*******************************************************************************
** Interface shared by meta-data objects which can be placed into an App.
+ ** e.g., Tables, Processes, and Apps themselves (since they can be nested)
*******************************************************************************/
public interface QAppChildMetaData
{
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java
index 22681a47..36ac02d6 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java
@@ -27,7 +27,8 @@ import java.util.List;
/*******************************************************************************
- **
+ ** MetaData definition of an App - an entity that organizes tables & processes
+ ** and can be arranged hierarchically (e.g, apps can contain other apps).
*******************************************************************************/
public class QAppMetaData implements QAppChildMetaData
{
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java
index 854e4b7c..0fec3d48 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java
@@ -30,7 +30,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.exceptions.QException;
-import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
@@ -467,17 +466,6 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
- /*******************************************************************************
- **
- *******************************************************************************/
- public QTableMetaData withInferredFieldBackendNames()
- {
- QInstanceEnricher.setInferredFieldBackendNames(this);
- return (this);
- }
-
-
-
/*******************************************************************************
** Getter for parentAppName
**
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLLoadFunction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLLoadFunction.java
index 5840c24e..f8e782f2 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLLoadFunction.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLLoadFunction.java
@@ -46,7 +46,7 @@ public class BasicETLLoadFunction implements BackendStep
private static final Logger LOG = LogManager.getLogger(BasicETLLoadFunction.class);
private QBackendTransaction transaction;
- private boolean returnStoredRecords = false;
+ private boolean returnStoredRecords = true;
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamed/StreamedETLBackendStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamed/StreamedETLBackendStep.java
index bb4337dc..ce2cfd7b 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamed/StreamedETLBackendStep.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamed/StreamedETLBackendStep.java
@@ -78,12 +78,12 @@ public class StreamedETLBackendStep implements BackendStep
// run the query action as an async job //
//////////////////////////////////////////
AsyncJobManager asyncJobManager = new AsyncJobManager();
- String queryJobUUID = asyncJobManager.startJob("ReportAction>QueryAction", (status) ->
+ String queryJobUUID = asyncJobManager.startJob("StreamedETL>QueryAction", (status) ->
{
basicETLExtractFunction.run(runBackendStepInput, runBackendStepOutput);
return (runBackendStepOutput);
});
- LOG.info("Started query job [" + queryJobUUID + "] for report");
+ LOG.info("Started query job [" + queryJobUUID + "] for streamed ETL");
AsyncJobState queryJobState = AsyncJobState.RUNNING;
AsyncJobStatus asyncJobStatus = null;
@@ -141,6 +141,14 @@ public class StreamedETLBackendStep implements BackendStep
LOG.info("Query job [" + queryJobUUID + "] for ETL completed with status: " + asyncJobStatus);
+ /////////////////////////////////////////
+ // propagate errors from the query job //
+ /////////////////////////////////////////
+ if(asyncJobStatus.getState().equals(AsyncJobState.ERROR))
+ {
+ throw (new QException("Query job failed with an error", asyncJobStatus.getCaughtException()));
+ }
+
//////////////////////////////////////////////////////
// send the final records to transform & load steps //
//////////////////////////////////////////////////////
@@ -207,6 +215,7 @@ public class StreamedETLBackendStep implements BackendStep
runBackendStepInput.setRecords(runBackendStepOutput.getRecords());
BasicETLLoadFunction basicETLLoadFunction = new BasicETLLoadFunction();
+ basicETLLoadFunction.setReturnStoredRecords(false);
basicETLLoadFunction.setTransaction(transaction);
basicETLLoadFunction.run(runBackendStepInput, runBackendStepOutput);
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java
index a33abaa1..08ecf054 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java
@@ -42,8 +42,8 @@ import com.kingsrook.qqq.backend.core.exceptions.QValueException;
*******************************************************************************/
public class ValueUtils
{
- private static final DateTimeFormatter yyyyMMddWithDashesFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
- private static final DateTimeFormatter MdyyyyWithSlashesFormatter = DateTimeFormatter.ofPattern("M/d/yyyy");
+ private static final DateTimeFormatter dateTimeFormatter_yyyyMMddWithDashes = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ private static final DateTimeFormatter dateTimeFormatter_MdyyyyWithSlashes = DateTimeFormatter.ofPattern("M/d/yyyy");
@@ -262,7 +262,7 @@ public class ValueUtils
private static LocalDate tryLocalDateParsers(String s)
{
DateTimeParseException lastException = null;
- for(DateTimeFormatter dateTimeFormatter : List.of(yyyyMMddWithDashesFormatter, MdyyyyWithSlashesFormatter))
+ for(DateTimeFormatter dateTimeFormatter : List.of(dateTimeFormatter_yyyyMMddWithDashes, dateTimeFormatter_MdyyyyWithSlashes))
{
try
{
@@ -386,7 +386,6 @@ public class ValueUtils
}
else if(value instanceof Calendar c)
{
- TimeZone tz = c.getTimeZone();
return (c.toInstant());
}
else if(value instanceof LocalDateTime ldt)
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java
index c86ef97e..c22da8a5 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java
@@ -30,6 +30,7 @@ import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
+import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
@@ -84,7 +85,7 @@ class MetaDataActionTest
QFrontendAppMetaData peopleApp = apps.get(TestUtils.APP_NAME_PEOPLE);
assertThat(peopleApp.getChildren()).isNotEmpty();
- Optional greetingsAppUnderPeopleFromMapOptional = peopleApp.getChildren().stream()
+ Optional greetingsAppUnderPeopleFromMapOptional = peopleApp.getChildren().stream()
.filter(e -> e.getName().equals(TestUtils.APP_NAME_GREETINGS)).findFirst();
assertThat(greetingsAppUnderPeopleFromMapOptional).isPresent();
@@ -97,18 +98,18 @@ class MetaDataActionTest
///////////////////////////////////////////////
// assert against the hierarchical apps tree //
///////////////////////////////////////////////
- List appTree = result.getAppTree();
- Set appNamesInTopOfTree = appTree.stream().map(QFrontendAppMetaData::getName).collect(Collectors.toSet());
+ List appTree = result.getAppTree();
+ Set appNamesInTopOfTree = appTree.stream().map(AppTreeNode::getName).collect(Collectors.toSet());
assertThat(appNamesInTopOfTree).contains(TestUtils.APP_NAME_PEOPLE);
assertThat(appNamesInTopOfTree).contains(TestUtils.APP_NAME_MISCELLANEOUS);
assertThat(appNamesInTopOfTree).doesNotContain(TestUtils.APP_NAME_GREETINGS);
- Optional peopleAppOptional = appTree.stream()
+ Optional peopleAppOptional = appTree.stream()
.filter(e -> e.getName().equals(TestUtils.APP_NAME_PEOPLE)).findFirst();
assertThat(peopleAppOptional).isPresent();
assertThat(peopleAppOptional.get().getChildren()).isNotEmpty();
- Optional greetingsAppUnderPeopleFromTree = peopleAppOptional.get().getChildren().stream()
+ Optional greetingsAppUnderPeopleFromTree = peopleAppOptional.get().getChildren().stream()
.filter(e -> e.getName().equals(TestUtils.APP_NAME_GREETINGS)).findFirst();
assertThat(greetingsAppUnderPeopleFromTree).isPresent();
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityTest.java
index be3cd897..2904fbd2 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityTest.java
@@ -26,6 +26,7 @@ import java.math.BigDecimal;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.testentities.Item;
import com.kingsrook.qqq.backend.core.model.data.testentities.ItemWithPrimitives;
+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;
@@ -177,6 +178,8 @@ class QRecordEntityTest
///////////////////////////////////////////////////////////////
// assert about attributes that came from @QField annotation //
///////////////////////////////////////////////////////////////
+ assertEquals("SKU", qTableMetaData.getField("sku").getLabel());
+ assertEquals(DisplayFormat.COMMAS, qTableMetaData.getField("quantity").getDisplayFormat());
assertTrue(qTableMetaData.getField("sku").getIsRequired());
assertFalse(qTableMetaData.getField("quantity").getIsEditable());
assertEquals("is_featured", qTableMetaData.getField("featured").getBackendName());
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/data/testentities/Item.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/data/testentities/Item.java
index 86653880..fd5225a0 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/data/testentities/Item.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/data/testentities/Item.java
@@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.data.testentities;
import java.math.BigDecimal;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
/*******************************************************************************
@@ -32,13 +33,13 @@ import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
*******************************************************************************/
public class Item extends QRecordEntity
{
- @QField(isRequired = true)
+ @QField(isRequired = true, label = "SKU")
private String sku;
@QField()
private String description;
- @QField(isEditable = false)
+ @QField(isEditable = false, displayFormat = DisplayFormat.COMMAS)
private Integer quantity;
private BigDecimal price;
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/Auth0AuthenticationModuleTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/Auth0AuthenticationModuleTest.java
index 049d3c57..bc69be9d 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/Auth0AuthenticationModuleTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/Auth0AuthenticationModuleTest.java
@@ -60,21 +60,8 @@ public class Auth0AuthenticationModuleTest
/*******************************************************************************
- ** Test an expired token where 'now' is set to a time that would not require it to be
- ** re-checked, so it'll show as valid
- **
- *******************************************************************************/
- @Test
- public void testLastTimeCheckedNow()
- {
- assertTrue(testLastTimeChecked(Instant.now(), UNDECODABLE_TOKEN), "A session just checked 'now' should always be valid");
- }
-
-
-
- /*******************************************************************************
- ** Test an expired token where 'now' is set to a time that would not require it to be
- ** re-checked, so it'll show as valid
+ ** Test a token where last-checked is set to a time that would not require it to be
+ ** re-checked, so it'll show as valid no matter what the token is.
**
*******************************************************************************/
@Test
@@ -87,8 +74,8 @@ public class Auth0AuthenticationModuleTest
/*******************************************************************************
- ** Test an expired token where 'now' is set to a time that would require it to be
- ** re-checked
+ ** Test a token where last-checked is set to a time that would require it to be
+ ** re-checked, so it'll show as invalid.
**
*******************************************************************************/
@Test
@@ -101,8 +88,8 @@ public class Auth0AuthenticationModuleTest
/*******************************************************************************
- ** Test an expired token where 'now' is set to a time that would require it to be
- ** re-checked
+ ** Test a token where last-checked is past the threshold, so it'll get re-checked,
+ ** and will fail.
**
*******************************************************************************/
@Test
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamed/StreamedETLProcessTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamed/StreamedETLProcessTest.java
index 97d756c9..ea09adba 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamed/StreamedETLProcessTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamed/StreamedETLProcessTest.java
@@ -55,7 +55,10 @@ class StreamedETLProcessTest
RunProcessOutput result = new RunProcessAction().execute(request);
assertNotNull(result);
- assertTrue(result.getRecords().stream().allMatch(r -> r.getValues().containsKey("id")), "records should have an id, set by the process");
+ ///////////////////////////////////////////////////////////////////////
+ // since this is streamed, assert there are no records in the output //
+ ///////////////////////////////////////////////////////////////////////
+ assertTrue(result.getRecords().isEmpty());
assertTrue(result.getException().isEmpty());
}
@@ -77,12 +80,14 @@ class StreamedETLProcessTest
// define our mapping from destination-table field names to source-table field names //
///////////////////////////////////////////////////////////////////////////////////////
QKeyBasedFieldMapping mapping = new QKeyBasedFieldMapping().withMapping("name", "firstName");
- // request.addValue(StreamedETLProcess.FIELD_MAPPING_JSON, JsonUtils.toJson(mapping.getMapping()));
request.addValue(StreamedETLProcess.FIELD_MAPPING_JSON, JsonUtils.toJson(mapping));
RunProcessOutput result = new RunProcessAction().execute(request);
assertNotNull(result);
- assertTrue(result.getRecords().stream().allMatch(r -> r.getValues().containsKey("id")), "records should have an id, set by the process");
+ ///////////////////////////////////////////////////////////////////////
+ // since this is streamed, assert there are no records in the output //
+ ///////////////////////////////////////////////////////////////////////
+ assertTrue(result.getRecords().isEmpty());
assertTrue(result.getException().isEmpty());
}
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java
index 0ef501a7..998fae6d 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java
@@ -53,7 +53,6 @@ class ValueUtilsTest
@Test
void testGetValueAsString() throws QValueException
{
- //noinspection ConstantConditions
assertNull(ValueUtils.getValueAsString(null));
assertEquals("", ValueUtils.getValueAsString(""));
assertEquals(" ", ValueUtils.getValueAsString(" "));
@@ -164,26 +163,28 @@ class ValueUtilsTest
@Test
void testGetValueAsLocalDate() throws QValueException
{
+ LocalDate expected = LocalDate.of(1980, Month.MAY, 31);
+
assertNull(ValueUtils.getValueAsLocalDate(null));
assertNull(ValueUtils.getValueAsLocalDate(""));
assertNull(ValueUtils.getValueAsLocalDate(" "));
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(LocalDate.of(1980, 5, 31)));
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new java.sql.Date(80, 4, 31)));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate(LocalDate.of(1980, 5, 31)));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate(new java.sql.Date(80, 4, 31)));
//noinspection MagicConstant
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new java.util.Date(80, 4, 31)));
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new java.util.Date(80, Calendar.MAY, 31)));
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new java.util.Date(80, Calendar.MAY, 31, 12, 0)));
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new java.util.Date(80, Calendar.MAY, 31, 4, 0)));
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new java.util.Date(80, Calendar.MAY, 31, 22, 0)));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate(new java.util.Date(80, 4, 31)));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate(new java.util.Date(80, Calendar.MAY, 31)));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate(new java.util.Date(80, Calendar.MAY, 31, 12, 0)));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate(new java.util.Date(80, Calendar.MAY, 31, 4, 0)));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate(new java.util.Date(80, Calendar.MAY, 31, 22, 0)));
//noinspection MagicConstant
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new GregorianCalendar(1980, 4, 31)));
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new GregorianCalendar(1980, Calendar.MAY, 31)));
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(LocalDateTime.of(1980, 5, 31, 12, 0)));
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(LocalDateTime.of(1980, 5, 31, 4, 0)));
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(LocalDateTime.of(1980, 5, 31, 22, 0)));
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(LocalDateTime.of(1980, Month.MAY, 31, 12, 0)));
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate("1980-05-31"));
- assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate("05/31/1980"));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate(new GregorianCalendar(1980, 4, 31)));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate(new GregorianCalendar(1980, Calendar.MAY, 31)));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate(LocalDateTime.of(1980, 5, 31, 12, 0)));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate(LocalDateTime.of(1980, 5, 31, 4, 0)));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate(LocalDateTime.of(1980, 5, 31, 22, 0)));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate(LocalDateTime.of(1980, Month.MAY, 31, 12, 0)));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate("1980-05-31"));
+ assertEquals(expected, ValueUtils.getValueAsLocalDate("05/31/1980"));
assertThrows(QValueException.class, () -> ValueUtils.getValueAsLocalDate("a"));
assertThrows(QValueException.class, () -> ValueUtils.getValueAsLocalDate("a,b"));
diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java
index b9e76476..7bdd715b 100644
--- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java
+++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java
@@ -172,8 +172,6 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte
{
LOG.info("Opening transaction");
Connection connection = getConnection(insertInput);
- connection.setAutoCommit(false);
- System.out.println("Set connection [" + connection + "] to auto-commit false");
return (new RDBMSTransaction(connection));
}
diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSTransaction.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSTransaction.java
index 529f69ff..0c7e50cf 100644
--- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSTransaction.java
+++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSTransaction.java
@@ -22,8 +22,8 @@
package com.kingsrook.qqq.backend.module.rdbms.actions;
-import java.io.IOException;
import java.sql.Connection;
+import java.sql.SQLException;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import org.apache.logging.log4j.LogManager;
@@ -46,8 +46,9 @@ public class RDBMSTransaction extends QBackendTransaction
/*******************************************************************************
**
*******************************************************************************/
- public RDBMSTransaction(Connection connection)
+ public RDBMSTransaction(Connection connection) throws SQLException
{
+ connection.setAutoCommit(false);
this.connection = connection;
}
@@ -73,7 +74,6 @@ public class RDBMSTransaction extends QBackendTransaction
try
{
RDBMSTransaction.LOG.info("Committing transaction");
- System.out.println("Calling commit on connection [" + connection + "]");
connection.commit();
RDBMSTransaction.LOG.info("Commit complete");
}
@@ -108,18 +108,7 @@ public class RDBMSTransaction extends QBackendTransaction
/*******************************************************************************
- * Closes this stream and releases any system resources associated
- * with it. If the stream is already closed then invoking this
- * method has no effect.
- *
- * As noted in {@link AutoCloseable#close()}, cases where the
- * close may fail require careful attention. It is strongly advised
- * to relinquish the underlying resources and to internally
- * mark the {@code Closeable} as closed, prior to throwing
- * the {@code IOException}.
- *
- * @throws IOException
- * if an I/O error occurs
+ **
*******************************************************************************/
@Override
public void close()
diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java
index e5961b38..b5572324 100644
--- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java
+++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java
@@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.module.rdbms.jdbc;
import java.io.Serializable;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
@@ -188,8 +189,6 @@ public class QueryManager
@SuppressWarnings("unchecked")
public static T executeStatementForSingleValue(Connection connection, Class returnClass, String sql, Object... params) throws SQLException
{
- throw (new NotImplementedException());
- /*
PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params);
statement.execute();
ResultSet resultSet = statement.getResultSet();
@@ -238,7 +237,6 @@ public class QueryManager
{
return (null);
}
- */
}
@@ -1397,6 +1395,38 @@ public class QueryManager
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static Instant getInstant(ResultSet resultSet, String column) throws SQLException
+ {
+ Timestamp value = resultSet.getTimestamp(column);
+ if(resultSet.wasNull())
+ {
+ return (null);
+ }
+
+ return (value.toInstant());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static Instant getInstant(ResultSet resultSet, int column) throws SQLException
+ {
+ Timestamp value = resultSet.getTimestamp(column);
+ if(resultSet.wasNull())
+ {
+ return (null);
+ }
+
+ return (value.toInstant());
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
diff --git a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/TestUtils.java b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/TestUtils.java
index 96b75742..18b38403 100644
--- a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/TestUtils.java
+++ b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/TestUtils.java
@@ -22,14 +22,22 @@
package com.kingsrook.qqq.backend.module.rdbms;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
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.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
+import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSActionTest;
+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 com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails;
+import org.apache.commons.io.IOUtils;
+import static junit.framework.Assert.assertNotNull;
/*******************************************************************************
@@ -42,6 +50,29 @@ public class TestUtils
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @SuppressWarnings("unchecked")
+ public static void primeTestDatabase(String sqlFileName) throws Exception
+ {
+ ConnectionManager connectionManager = new ConnectionManager();
+ try(Connection connection = connectionManager.getConnection(TestUtils.defineBackend()))
+ {
+ InputStream primeTestDatabaseSqlStream = RDBMSActionTest.class.getResourceAsStream("/" + sqlFileName);
+ assertNotNull(primeTestDatabaseSqlStream);
+ List lines = (List) IOUtils.readLines(primeTestDatabaseSqlStream);
+ lines = lines.stream().filter(line -> !line.startsWith("-- ")).toList();
+ String joinedSQL = String.join("\n", lines);
+ for(String sql : joinedSQL.split(";"))
+ {
+ QueryManager.executeUpdate(connection, sql);
+ }
+ }
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
diff --git a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSActionTest.java b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSActionTest.java
index 7382de50..398f6f5f 100644
--- a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSActionTest.java
+++ b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSActionTest.java
@@ -22,15 +22,11 @@
package com.kingsrook.qqq.backend.module.rdbms.actions;
-import java.io.InputStream;
import java.sql.Connection;
-import java.util.List;
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
-import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.AfterEach;
-import static junit.framework.Assert.assertNotNull;
/*******************************************************************************
@@ -57,33 +53,11 @@ public class RDBMSActionTest
*******************************************************************************/
protected void primeTestDatabase() throws Exception
{
- primeTestDatabase("prime-test-database.sql");
+ TestUtils.primeTestDatabase("prime-test-database.sql");
}
- /*******************************************************************************
- **
- *******************************************************************************/
- @SuppressWarnings("unchecked")
- protected void primeTestDatabase(String sqlFileName) throws Exception
- {
- ConnectionManager connectionManager = new ConnectionManager();
- try(Connection connection = connectionManager.getConnection(TestUtils.defineBackend()))
- {
- InputStream primeTestDatabaseSqlStream = RDBMSActionTest.class.getResourceAsStream("/" + sqlFileName);
- assertNotNull(primeTestDatabaseSqlStream);
- List lines = (List) IOUtils.readLines(primeTestDatabaseSqlStream);
- lines = lines.stream().filter(line -> !line.startsWith("-- ")).toList();
- String joinedSQL = String.join("\n", lines);
- for(String sql : joinedSQL.split(";"))
- {
- QueryManager.executeUpdate(connection, sql);
- }
- }
- }
-
-
/*******************************************************************************
**
diff --git a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteActionTest.java b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteActionTest.java
index 0bd18c51..d006bf4c 100644
--- a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteActionTest.java
+++ b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteActionTest.java
@@ -151,7 +151,7 @@ public class RDBMSDeleteActionTest extends RDBMSActionTest
//////////////////////////////////////////////////////////////////
// load the parent-child tables, with foreign keys and instance //
//////////////////////////////////////////////////////////////////
- super.primeTestDatabase("prime-test-database-parent-child-tables.sql");
+ TestUtils.primeTestDatabase("prime-test-database-parent-child-tables.sql");
DeleteInput deleteInput = initChildTableInstanceAndDeleteRequest();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSTransactionTest.java b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSTransactionTest.java
new file mode 100644
index 00000000..7357cb4c
--- /dev/null
+++ b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSTransactionTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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 .
+ */
+
+package com.kingsrook.qqq.backend.module.rdbms.actions;
+
+
+import java.sql.Connection;
+import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
+import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
+import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+/*******************************************************************************
+ ** Unit test for RDBMSTransaction
+ *******************************************************************************/
+class RDBMSTransactionTest
+{
+ private final String testToken = getClass().getName();
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @BeforeEach
+ protected void beforeEach() throws Exception
+ {
+ TestUtils.primeTestDatabase("prime-test-database.sql");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testCommit() throws Exception
+ {
+ ConnectionManager connectionManager = new ConnectionManager();
+ Connection connection = connectionManager.getConnection(TestUtils.defineBackend());
+ Integer preCount = QueryManager.executeStatementForSingleValue(connection, Integer.class, "SELECT COUNT(*) FROM person");
+
+ Connection connectionForTransaction = connectionManager.getConnection(TestUtils.defineBackend());
+ RDBMSTransaction transaction = new RDBMSTransaction(connectionForTransaction);
+
+ QueryManager.executeUpdate(transaction.getConnection(), "INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)", testToken, testToken, testToken);
+ transaction.commit();
+
+ Integer postCount = QueryManager.executeStatementForSingleValue(connection, Integer.class, "SELECT COUNT(*) FROM person");
+ assertEquals(preCount + 1, postCount);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testRollback() throws Exception
+ {
+ ConnectionManager connectionManager = new ConnectionManager();
+ Connection connection = connectionManager.getConnection(TestUtils.defineBackend());
+ Integer preCount = QueryManager.executeStatementForSingleValue(connection, Integer.class, "SELECT COUNT(*) FROM person");
+
+ Connection connectionForTransaction = connectionManager.getConnection(TestUtils.defineBackend());
+ RDBMSTransaction transaction = new RDBMSTransaction(connectionForTransaction);
+
+ QueryManager.executeUpdate(transaction.getConnection(), "INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)", testToken, testToken, testToken);
+ transaction.rollback();
+
+ Integer postCount = QueryManager.executeStatementForSingleValue(connection, Integer.class, "SELECT COUNT(*) FROM person");
+ assertEquals(preCount, postCount);
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManagerTest.java b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManagerTest.java
index e62ac2ae..ea5e99cc 100644
--- a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManagerTest.java
+++ b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManagerTest.java
@@ -329,4 +329,35 @@ class QueryManagerTest
assertEquals(59, localTime.getSecond(), "Second value");
}
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testExecuteStatementForSingleValue() 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')
+ """);
+ assertEquals(null, QueryManager.executeStatementForSingleValue(connection, Integer.class, "SELECT int_col FROM test_table WHERE int_col = -1"));
+ assertEquals(1, QueryManager.executeStatementForSingleValue(connection, Integer.class, "SELECT COUNT(*) FROM test_table"));
+ assertEquals(47, QueryManager.executeStatementForSingleValue(connection, Integer.class, "SELECT int_col FROM test_table"));
+ assertEquals("Q", QueryManager.executeStatementForSingleValue(connection, String.class, "SELECT char_col FROM test_table"));
+ assertEquals(new BigDecimal("1.1"), QueryManager.executeStatementForSingleValue(connection, BigDecimal.class, "SELECT 1.1 FROM test_table"));
+ assertEquals(1, QueryManager.executeStatementForSingleValue(connection, Integer.class, "SELECT 1.1 FROM test_table"));
+
+ QueryManager.executeUpdate(connection, """
+ INSERT INTO test_table
+ ( int_col, datetime_col, char_col, date_col, time_col )
+ VALUES
+ ( null, null, null, null, null)
+ """);
+ assertEquals(null, QueryManager.executeStatementForSingleValue(connection, Integer.class, "SELECT int_col FROM test_table WHERE int_col IS NULL"));
+ }
+
}
\ No newline at end of file
diff --git a/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleMetaDataProvider.java b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleMetaDataProvider.java
index ed82ff44..2c9bb956 100644
--- a/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleMetaDataProvider.java
+++ b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleMetaDataProvider.java
@@ -26,6 +26,7 @@ import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
+import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
@@ -227,7 +228,7 @@ public class SampleMetaDataProvider
table.addField(new QFieldMetaData("company_code", QFieldType.STRING) // todo PVS
.withLabel("Company")
.withIsRequired(true)
- .withBackendName("comp_code"));
+ .withBackendName("company_code"));
table.addField(new QFieldMetaData("service_level", QFieldType.STRING) // todo PVS
.withLabel("Service Level")
@@ -246,7 +247,7 @@ public class SampleMetaDataProvider
*******************************************************************************/
public static QTableMetaData defineTablePerson()
{
- return new QTableMetaData()
+ QTableMetaData qTableMetaData = new QTableMetaData()
.withName(TABLE_NAME_PERSON)
.withLabel("Person")
.withBackendName(RDBMS_BACKEND_NAME)
@@ -266,9 +267,11 @@ public class SampleMetaDataProvider
.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("dates", "Dates", new QIcon("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")))
+ .withSection(new QFieldSection("dates", "Dates", new QIcon("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
- .withInferredFieldBackendNames();
+ QInstanceEnricher.setInferredFieldBackendNames(qTableMetaData);
+
+ return (qTableMetaData);
}