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: + **

*******************************************************************************/ 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); }