mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Initial support for associated records (implemented insert, delete).
Include "api" on audit.
This commit is contained in:
@ -54,6 +54,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
@ -106,6 +107,13 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSession qSession = QContext.getQSession();
|
||||||
|
String apiVersion = qSession.getValue("apiVersion");
|
||||||
|
if(apiVersion != null)
|
||||||
|
{
|
||||||
|
contextSuffix += (" via API Version: " + apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
AuditInput auditInput = new AuditInput();
|
AuditInput auditInput = new AuditInput();
|
||||||
if(auditLevel.equals(AuditLevel.RECORD) || (auditLevel.equals(AuditLevel.FIELD) && !dmlType.supportsFields))
|
if(auditLevel.equals(AuditLevel.RECORD) || (auditLevel.equals(AuditLevel.FIELD) && !dmlType.supportsFields))
|
||||||
{
|
{
|
||||||
@ -125,7 +133,7 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
|||||||
///////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
// do many audits, all with field level details, for FIELD level //
|
// do many audits, all with field level details, for FIELD level //
|
||||||
///////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
QPossibleValueTranslator qPossibleValueTranslator = new QPossibleValueTranslator(QContext.getQInstance(), QContext.getQSession());
|
QPossibleValueTranslator qPossibleValueTranslator = new QPossibleValueTranslator(QContext.getQInstance(), qSession);
|
||||||
qPossibleValueTranslator.translatePossibleValuesInRecords(table, CollectionUtils.mergeLists(recordList, oldRecordList));
|
qPossibleValueTranslator.translatePossibleValuesInRecords(table, CollectionUtils.mergeLists(recordList, oldRecordList));
|
||||||
|
|
||||||
//////////////////////////////////////////
|
//////////////////////////////////////////
|
||||||
|
@ -28,6 +28,7 @@ import java.util.List;
|
|||||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.audits.DMLAuditInput;
|
import com.kingsrook.qqq.backend.core.model.actions.audits.DMLAuditInput;
|
||||||
@ -40,6 +41,9 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
@ -91,6 +95,9 @@ public class DeleteAction
|
|||||||
List<QRecord> recordListForAudit = getRecordListForAuditIfNeeded(deleteInput);
|
List<QRecord> recordListForAudit = getRecordListForAuditIfNeeded(deleteInput);
|
||||||
|
|
||||||
DeleteOutput deleteOutput = deleteInterface.execute(deleteInput);
|
DeleteOutput deleteOutput = deleteInterface.execute(deleteInput);
|
||||||
|
|
||||||
|
manageAssociations(deleteInput);
|
||||||
|
|
||||||
// todo post-customization - can do whatever w/ the result if you want
|
// todo post-customization - can do whatever w/ the result if you want
|
||||||
|
|
||||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(deleteInput).withRecordList(recordListForAudit));
|
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(deleteInput).withRecordList(recordListForAudit));
|
||||||
@ -100,6 +107,46 @@ public class DeleteAction
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void manageAssociations(DeleteInput deleteInput) throws QException
|
||||||
|
{
|
||||||
|
QTableMetaData table = deleteInput.getTable();
|
||||||
|
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||||
|
{
|
||||||
|
// e.g., order -> orderLine
|
||||||
|
QJoinMetaData join = QContext.getQInstance().getJoin(association.getJoinName()); // todo ... ever need to flip?
|
||||||
|
// just assume this, at least for now... if(BooleanUtils.isTrue(association.getDoInserts()))
|
||||||
|
|
||||||
|
QQueryFilter filter = new QQueryFilter();
|
||||||
|
|
||||||
|
if(join.getJoinOns().size() == 1 && join.getJoinOns().get(0).getLeftField().equals(table.getPrimaryKeyField()))
|
||||||
|
{
|
||||||
|
filter.addCriteria(new QFilterCriteria(join.getJoinOns().get(0).getRightField(), QCriteriaOperator.IN, deleteInput.getPrimaryKeys()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw (new QException("Join of this type is not supported for an associated delete at this time..."));
|
||||||
|
}
|
||||||
|
|
||||||
|
QTableMetaData associatedTable = QContext.getQInstance().getTable(association.getAssociatedTableName());
|
||||||
|
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(association.getAssociatedTableName());
|
||||||
|
queryInput.setFilter(filter);
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
List<Serializable> associatedKeys = queryOutput.getRecords().stream().map(r -> r.getValue(associatedTable.getPrimaryKeyField())).toList();
|
||||||
|
|
||||||
|
DeleteInput nextLevelDeleteInput = new DeleteInput();
|
||||||
|
nextLevelDeleteInput.setTableName(association.getAssociatedTableName());
|
||||||
|
nextLevelDeleteInput.setPrimaryKeys(associatedKeys);
|
||||||
|
DeleteOutput nextLevelDeleteOutput = new DeleteAction().execute(nextLevelDeleteInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
|||||||
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -40,12 +41,16 @@ import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
|||||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UniqueKeyHelper;
|
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UniqueKeyHelper;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.audits.DMLAuditInput;
|
import com.kingsrook.qqq.backend.core.model.actions.audits.DMLAuditInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||||
@ -75,15 +80,16 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
|||||||
Optional<AbstractPostInsertCustomizer> postInsertCustomizer = QCodeLoader.getTableCustomizer(AbstractPostInsertCustomizer.class, table, TableCustomizers.POST_INSERT_RECORD.getRole());
|
Optional<AbstractPostInsertCustomizer> postInsertCustomizer = QCodeLoader.getTableCustomizer(AbstractPostInsertCustomizer.class, table, TableCustomizers.POST_INSERT_RECORD.getRole());
|
||||||
setAutomationStatusField(insertInput);
|
setAutomationStatusField(insertInput);
|
||||||
|
|
||||||
ValueBehaviorApplier.applyFieldBehaviors(insertInput.getInstance(), table, insertInput.getRecords());
|
|
||||||
// todo - need to handle records with errors coming out of here...
|
|
||||||
|
|
||||||
QBackendModuleInterface qModule = getBackendModuleInterface(insertInput);
|
QBackendModuleInterface qModule = getBackendModuleInterface(insertInput);
|
||||||
// todo pre-customization - just get to modify the request?
|
// todo pre-customization - just get to modify the request?
|
||||||
|
|
||||||
|
ValueBehaviorApplier.applyFieldBehaviors(insertInput.getInstance(), table, insertInput.getRecords());
|
||||||
setErrorsIfUniqueKeyErrors(insertInput, table);
|
setErrorsIfUniqueKeyErrors(insertInput, table);
|
||||||
|
|
||||||
InsertOutput insertOutput = qModule.getInsertInterface().execute(insertInput);
|
InsertOutput insertOutput = qModule.getInsertInterface().execute(insertInput);
|
||||||
|
|
||||||
|
manageAssociations(table, insertOutput.getRecords());
|
||||||
|
|
||||||
// todo post-customization - can do whatever w/ the result if you want
|
// todo post-customization - can do whatever w/ the result if you want
|
||||||
|
|
||||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(insertInput).withRecordList(insertOutput.getRecords()));
|
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(insertInput).withRecordList(insertOutput.getRecords()));
|
||||||
@ -98,6 +104,43 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void manageAssociations(QTableMetaData table, List<QRecord> insertedRecords) throws QException
|
||||||
|
{
|
||||||
|
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||||
|
{
|
||||||
|
// e.g., order -> orderLine
|
||||||
|
QJoinMetaData join = QContext.getQInstance().getJoin(association.getJoinName()); // todo ... ever need to flip?
|
||||||
|
// just assume this, at least for now... if(BooleanUtils.isTrue(association.getDoInserts()))
|
||||||
|
|
||||||
|
List<QRecord> nextLevelInserts = new ArrayList<>();
|
||||||
|
for(QRecord record : insertedRecords)
|
||||||
|
{
|
||||||
|
if(record.getAssociatedRecords() != null && record.getAssociatedRecords().containsKey(association.getName()))
|
||||||
|
{
|
||||||
|
for(QRecord associatedRecord : CollectionUtils.nonNullList(record.getAssociatedRecords().get(association.getName())))
|
||||||
|
{
|
||||||
|
for(JoinOn joinOn : join.getJoinOns())
|
||||||
|
{
|
||||||
|
associatedRecord.setValue(joinOn.getRightField(), record.getValue(joinOn.getLeftField()));
|
||||||
|
}
|
||||||
|
nextLevelInserts.add(associatedRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InsertInput nextLevelInsertInput = new InsertInput();
|
||||||
|
nextLevelInsertInput.setTableName(association.getAssociatedTableName());
|
||||||
|
nextLevelInsertInput.setRecords(nextLevelInserts);
|
||||||
|
InsertOutput nextLevelInsertOutput = new InsertAction().execute(nextLevelInsertInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -28,6 +28,7 @@ import java.time.Instant;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -65,6 +66,8 @@ public class QRecord implements Serializable
|
|||||||
private Map<String, Serializable> backendDetails = new LinkedHashMap<>();
|
private Map<String, Serializable> backendDetails = new LinkedHashMap<>();
|
||||||
private List<String> errors = new ArrayList<>();
|
private List<String> errors = new ArrayList<>();
|
||||||
|
|
||||||
|
private Map<String, List<QRecord>> associatedRecords = new HashMap<>();
|
||||||
|
|
||||||
public static final String BACKEND_DETAILS_TYPE_JSON_SOURCE_OBJECT = "jsonSourceObject";
|
public static final String BACKEND_DETAILS_TYPE_JSON_SOURCE_OBJECT = "jsonSourceObject";
|
||||||
|
|
||||||
|
|
||||||
@ -102,6 +105,7 @@ public class QRecord implements Serializable
|
|||||||
this.displayValues = doDeepCopy(record.displayValues);
|
this.displayValues = doDeepCopy(record.displayValues);
|
||||||
this.backendDetails = doDeepCopy(record.backendDetails);
|
this.backendDetails = doDeepCopy(record.backendDetails);
|
||||||
this.errors = doDeepCopy(record.errors);
|
this.errors = doDeepCopy(record.errors);
|
||||||
|
this.associatedRecords = doDeepCopy(record.associatedRecords);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -575,4 +579,66 @@ public class QRecord implements Serializable
|
|||||||
return (QRecordEntity.fromQRecord(c, this));
|
return (QRecordEntity.fromQRecord(c, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for associatedRecords
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, List<QRecord>> getAssociatedRecords()
|
||||||
|
{
|
||||||
|
return (this.associatedRecords);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for associatedRecords
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setAssociatedRecords(Map<String, List<QRecord>> associatedRecords)
|
||||||
|
{
|
||||||
|
this.associatedRecords = associatedRecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for associatedRecords
|
||||||
|
*******************************************************************************/
|
||||||
|
public QRecord withAssociatedRecords(Map<String, List<QRecord>> associatedRecords)
|
||||||
|
{
|
||||||
|
this.associatedRecords = associatedRecords;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for associatedRecords
|
||||||
|
*******************************************************************************/
|
||||||
|
public QRecord withAssociatedRecords(String name, List<QRecord> associatedRecords)
|
||||||
|
{
|
||||||
|
if(this.associatedRecords == null)
|
||||||
|
{
|
||||||
|
this.associatedRecords = new HashMap<>();
|
||||||
|
}
|
||||||
|
this.associatedRecords.put(name, associatedRecords);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for associatedRecord
|
||||||
|
*******************************************************************************/
|
||||||
|
public QRecord withAssociatedRecord(String name, QRecord associatedRecord)
|
||||||
|
{
|
||||||
|
if(this.associatedRecords == null)
|
||||||
|
{
|
||||||
|
this.associatedRecords = new HashMap<>();
|
||||||
|
}
|
||||||
|
this.associatedRecords.putIfAbsent(name, new ArrayList<>());
|
||||||
|
this.associatedRecords.get(name).add(associatedRecord);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** definition of a qqq table that is "associated" with another table, e.g.,
|
||||||
|
** managed along with it - such as child-records under a parent record.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class Association
|
||||||
|
{
|
||||||
|
private String name;
|
||||||
|
private String associatedTableName;
|
||||||
|
private String joinName;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for name
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return (this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for name
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for name
|
||||||
|
*******************************************************************************/
|
||||||
|
public Association withName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for associatedTableName
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getAssociatedTableName()
|
||||||
|
{
|
||||||
|
return (this.associatedTableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for associatedTableName
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setAssociatedTableName(String associatedTableName)
|
||||||
|
{
|
||||||
|
this.associatedTableName = associatedTableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for associatedTableName
|
||||||
|
*******************************************************************************/
|
||||||
|
public Association withAssociatedTableName(String associatedTableName)
|
||||||
|
{
|
||||||
|
this.associatedTableName = associatedTableName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for joinName
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getJoinName()
|
||||||
|
{
|
||||||
|
return (this.joinName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for joinName
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setJoinName(String joinName)
|
||||||
|
{
|
||||||
|
this.joinName = joinName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for joinName
|
||||||
|
*******************************************************************************/
|
||||||
|
public Association withJoinName(String joinName)
|
||||||
|
{
|
||||||
|
this.joinName = joinName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -72,6 +72,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
|||||||
|
|
||||||
private Map<String, QFieldMetaData> fields;
|
private Map<String, QFieldMetaData> fields;
|
||||||
private List<UniqueKey> uniqueKeys;
|
private List<UniqueKey> uniqueKeys;
|
||||||
|
private List<Association> associations;
|
||||||
|
|
||||||
private List<RecordSecurityLock> recordSecurityLocks;
|
private List<RecordSecurityLock> recordSecurityLocks;
|
||||||
private QPermissionRules permissionRules;
|
private QPermissionRules permissionRules;
|
||||||
@ -1237,4 +1238,50 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for associations
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<Association> getAssociations()
|
||||||
|
{
|
||||||
|
return (this.associations);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for associations
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setAssociations(List<Association> associations)
|
||||||
|
{
|
||||||
|
this.associations = associations;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for associations
|
||||||
|
*******************************************************************************/
|
||||||
|
public QTableMetaData withAssociations(List<Association> associations)
|
||||||
|
{
|
||||||
|
this.associations = associations;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for associations
|
||||||
|
*******************************************************************************/
|
||||||
|
public QTableMetaData withAssociation(Association association)
|
||||||
|
{
|
||||||
|
if(this.associations == null)
|
||||||
|
{
|
||||||
|
this.associations = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.associations.add(association);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,10 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.audits.AuditsMetaDataProvider;
|
import com.kingsrook.qqq.backend.core.model.audits.AuditsMetaDataProvider;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
@ -149,4 +152,145 @@ class DeleteActionTest extends BaseTest
|
|||||||
assertTrue(audits.stream().allMatch(r -> r.getValueString("message").equals("Record was Deleted")));
|
assertTrue(audits.stream().allMatch(r -> r.getValueString("message").equals("Record was Deleted")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testAssociatedDeletes() throws QException
|
||||||
|
{
|
||||||
|
{
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
insertInput.setRecords(List.of(
|
||||||
|
new QRecord().withValue("id", 1),
|
||||||
|
new QRecord().withValue("id", 2),
|
||||||
|
new QRecord().withValue("id", 3)
|
||||||
|
));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
|
||||||
|
insertInput.setRecords(List.of(
|
||||||
|
new QRecord().withValue("id", 1).withValue("orderId", 1),
|
||||||
|
new QRecord().withValue("id", 2).withValue("orderId", 1),
|
||||||
|
new QRecord().withValue("id", 3).withValue("orderId", 1),
|
||||||
|
new QRecord().withValue("id", 4).withValue("orderId", 1),
|
||||||
|
new QRecord().withValue("id", 5).withValue("orderId", 3),
|
||||||
|
new QRecord().withValue("id", 6).withValue("orderId", 3),
|
||||||
|
new QRecord().withValue("id", 7).withValue("orderId", 3)
|
||||||
|
));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM);
|
||||||
|
insertInput.setRecords(List.of(
|
||||||
|
new QRecord().withValue("id", 1).withValue("orderId", 1),
|
||||||
|
new QRecord().withValue("id", 2).withValue("orderId", 1),
|
||||||
|
new QRecord().withValue("id", 3).withValue("orderId", 2),
|
||||||
|
new QRecord().withValue("id", 4).withValue("orderId", 3),
|
||||||
|
new QRecord().withValue("id", 5).withValue("orderId", 3),
|
||||||
|
new QRecord().withValue("id", 6).withValue("orderId", 3)
|
||||||
|
));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
|
||||||
|
insertInput.setRecords(List.of(
|
||||||
|
new QRecord().withValue("id", 1).withValue("lineItemId", 1), // orderId: 1
|
||||||
|
new QRecord().withValue("id", 2).withValue("lineItemId", 1), // orderId: 1
|
||||||
|
new QRecord().withValue("id", 3).withValue("lineItemId", 2), // orderId: 1
|
||||||
|
new QRecord().withValue("id", 4).withValue("lineItemId", 2), // orderId: 1
|
||||||
|
new QRecord().withValue("id", 5).withValue("lineItemId", 3), // orderId: 2
|
||||||
|
new QRecord().withValue("id", 6).withValue("lineItemId", 3), // orderId: 2
|
||||||
|
new QRecord().withValue("id", 7).withValue("lineItemId", 4), // orderId: 3
|
||||||
|
new QRecord().withValue("id", 8).withValue("lineItemId", 5), // orderId: 3
|
||||||
|
new QRecord().withValue("id", 9).withValue("lineItemId", 6) // orderId: 3
|
||||||
|
));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
// assert about how many things we originally inserted //
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
assertEquals(3, queryTable(TestUtils.TABLE_NAME_ORDER).size());
|
||||||
|
assertEquals(7, queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC).size());
|
||||||
|
assertEquals(6, queryTable(TestUtils.TABLE_NAME_LINE_ITEM).size());
|
||||||
|
assertEquals(9, queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC).size());
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// delete (cascading) order 1 //
|
||||||
|
////////////////////////////////
|
||||||
|
DeleteInput deleteInput = new DeleteInput();
|
||||||
|
deleteInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
deleteInput.setPrimaryKeys(List.of(1));
|
||||||
|
new DeleteAction().execute(deleteInput);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
// assert that the associated data were deleted //
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
assertEquals(2, queryTable(TestUtils.TABLE_NAME_ORDER).size());
|
||||||
|
assertEquals(3, queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC).size());
|
||||||
|
assertEquals(4, queryTable(TestUtils.TABLE_NAME_LINE_ITEM).size());
|
||||||
|
assertEquals(5, queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC).size());
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
// delete order 2 //
|
||||||
|
////////////////////
|
||||||
|
deleteInput.setPrimaryKeys(List.of(2));
|
||||||
|
new DeleteAction().execute(deleteInput);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
// assert that the associated data were deleted //
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
assertEquals(1, queryTable(TestUtils.TABLE_NAME_ORDER).size());
|
||||||
|
assertEquals(3, queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC).size());
|
||||||
|
assertEquals(3, queryTable(TestUtils.TABLE_NAME_LINE_ITEM).size());
|
||||||
|
assertEquals(3, queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC).size());
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
// delete order 3 //
|
||||||
|
////////////////////
|
||||||
|
deleteInput.setPrimaryKeys(List.of(3));
|
||||||
|
new DeleteAction().execute(deleteInput);
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
// everything is deleted now //
|
||||||
|
///////////////////////////////
|
||||||
|
assertEquals(0, queryTable(TestUtils.TABLE_NAME_ORDER).size());
|
||||||
|
assertEquals(0, queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC).size());
|
||||||
|
assertEquals(0, queryTable(TestUtils.TABLE_NAME_LINE_ITEM).size());
|
||||||
|
assertEquals(0, queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC).size());
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
// make sure no errors if we try more deletes //
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
deleteInput.setPrimaryKeys(List.of(3));
|
||||||
|
new DeleteAction().execute(deleteInput);
|
||||||
|
|
||||||
|
deleteInput.setPrimaryKeys(List.of(1, 2, 3, 4));
|
||||||
|
new DeleteAction().execute(deleteInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static List<QRecord> queryTable(String tableName) throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(tableName);
|
||||||
|
queryInput.setFilter(new QQueryFilter().withOrderBy(new QFilterOrderBy("id")));
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
return (queryOutput.getRecords());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -209,4 +209,78 @@ class InsertActionTest extends BaseTest
|
|||||||
assertTrue(CollectionUtils.nullSafeIsEmpty(insertOutput.getRecords().get(1).getErrors()));
|
assertTrue(CollectionUtils.nullSafeIsEmpty(insertOutput.getRecords().get(1).getErrors()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testInsertAssociations() throws QException
|
||||||
|
{
|
||||||
|
QInstance qInstance = QContext.getQInstance();
|
||||||
|
QContext.getQSession().withSecurityKeyValue("storeId", 1);
|
||||||
|
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
insertInput.setRecords(List.of(
|
||||||
|
new QRecord().withValue("storeId", 1).withValue("orderNo", "ORD123")
|
||||||
|
|
||||||
|
.withAssociatedRecord("orderLine", new QRecord().withValue("sku", "BASIC1").withValue("quantity", 1)
|
||||||
|
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "LINE-EXT-1.1").withValue("value", "LINE-VAL-1")))
|
||||||
|
|
||||||
|
.withAssociatedRecord("orderLine", new QRecord().withValue("sku", "BASIC2").withValue("quantity", 2)
|
||||||
|
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "LINE-EXT-2.1").withValue("value", "LINE-VAL-2"))
|
||||||
|
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "LINE-EXT-2.2").withValue("value", "LINE-VAL-3")))
|
||||||
|
|
||||||
|
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "MY-FIELD-1").withValue("value", "MY-VALUE-1"))
|
||||||
|
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "MY-FIELD-2").withValue("value", "MY-VALUE-2"))
|
||||||
|
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "MY-FIELD-3").withValue("value", "MY-VALUE-3")),
|
||||||
|
|
||||||
|
new QRecord().withValue("storeId", 1).withValue("orderNo", "ORD124")
|
||||||
|
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "YOUR-FIELD-1").withValue("value", "YOUR-VALUE-1"))
|
||||||
|
));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
|
||||||
|
List<QRecord> orders = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER);
|
||||||
|
assertEquals(2, orders.size());
|
||||||
|
assertEquals(1, orders.get(0).getValueInteger("id"));
|
||||||
|
assertEquals(2, orders.get(1).getValueInteger("id"));
|
||||||
|
assertEquals("ORD123", orders.get(0).getValueString("orderNo"));
|
||||||
|
assertEquals("ORD124", orders.get(1).getValueString("orderNo"));
|
||||||
|
|
||||||
|
List<QRecord> orderLines = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM);
|
||||||
|
assertEquals(2, orderLines.size());
|
||||||
|
assertEquals(1, orderLines.get(0).getValueInteger("orderId"));
|
||||||
|
assertEquals(1, orderLines.get(1).getValueInteger("orderId"));
|
||||||
|
assertEquals("BASIC1", orderLines.get(0).getValueString("sku"));
|
||||||
|
assertEquals("BASIC2", orderLines.get(1).getValueString("sku"));
|
||||||
|
|
||||||
|
List<QRecord> orderExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
|
||||||
|
assertEquals(4, orderExtrinsics.size());
|
||||||
|
assertEquals(1, orderExtrinsics.get(0).getValueInteger("orderId"));
|
||||||
|
assertEquals(1, orderExtrinsics.get(1).getValueInteger("orderId"));
|
||||||
|
assertEquals(1, orderExtrinsics.get(2).getValueInteger("orderId"));
|
||||||
|
assertEquals(2, orderExtrinsics.get(3).getValueInteger("orderId"));
|
||||||
|
assertEquals("MY-FIELD-1", orderExtrinsics.get(0).getValueString("key"));
|
||||||
|
assertEquals("MY-FIELD-2", orderExtrinsics.get(1).getValueString("key"));
|
||||||
|
assertEquals("MY-FIELD-3", orderExtrinsics.get(2).getValueString("key"));
|
||||||
|
assertEquals("YOUR-FIELD-1", orderExtrinsics.get(3).getValueString("key"));
|
||||||
|
assertEquals("MY-VALUE-1", orderExtrinsics.get(0).getValueString("value"));
|
||||||
|
assertEquals("MY-VALUE-2", orderExtrinsics.get(1).getValueString("value"));
|
||||||
|
assertEquals("MY-VALUE-3", orderExtrinsics.get(2).getValueString("value"));
|
||||||
|
assertEquals("YOUR-VALUE-1", orderExtrinsics.get(3).getValueString("value"));
|
||||||
|
|
||||||
|
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
|
||||||
|
assertEquals(3, lineItemExtrinsics.size());
|
||||||
|
assertEquals(1, lineItemExtrinsics.get(0).getValueInteger("lineItemId"));
|
||||||
|
assertEquals(2, lineItemExtrinsics.get(1).getValueInteger("lineItemId"));
|
||||||
|
assertEquals(2, lineItemExtrinsics.get(2).getValueInteger("lineItemId"));
|
||||||
|
assertEquals("LINE-EXT-1.1", lineItemExtrinsics.get(0).getValueString("key"));
|
||||||
|
assertEquals("LINE-EXT-2.1", lineItemExtrinsics.get(1).getValueString("key"));
|
||||||
|
assertEquals("LINE-EXT-2.2", lineItemExtrinsics.get(2).getValueString("key"));
|
||||||
|
assertEquals("LINE-VAL-1", lineItemExtrinsics.get(0).getValueString("value"));
|
||||||
|
assertEquals("LINE-VAL-2", lineItemExtrinsics.get(1).getValueString("value"));
|
||||||
|
assertEquals("LINE-VAL-3", lineItemExtrinsics.get(2).getValueString("value"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.reporting.ReportType;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
|
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTracking;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTracking;
|
||||||
@ -130,6 +131,8 @@ public class TestUtils
|
|||||||
public static final String TABLE_NAME_SHAPE = "shape";
|
public static final String TABLE_NAME_SHAPE = "shape";
|
||||||
public static final String TABLE_NAME_ORDER = "order";
|
public static final String TABLE_NAME_ORDER = "order";
|
||||||
public static final String TABLE_NAME_LINE_ITEM = "orderLine";
|
public static final String TABLE_NAME_LINE_ITEM = "orderLine";
|
||||||
|
public static final String TABLE_NAME_LINE_ITEM_EXTRINSIC = "orderLineExtrinsic";
|
||||||
|
public static final String TABLE_NAME_ORDER_EXTRINSIC = "orderExtrinsic";
|
||||||
|
|
||||||
public static final String PROCESS_NAME_GREET_PEOPLE = "greet";
|
public static final String PROCESS_NAME_GREET_PEOPLE = "greet";
|
||||||
public static final String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive";
|
public static final String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive";
|
||||||
@ -185,8 +188,12 @@ public class TestUtils
|
|||||||
qInstance.addTable(defineTableBasepull());
|
qInstance.addTable(defineTableBasepull());
|
||||||
qInstance.addTable(defineTableOrder());
|
qInstance.addTable(defineTableOrder());
|
||||||
qInstance.addTable(defineTableLineItem());
|
qInstance.addTable(defineTableLineItem());
|
||||||
|
qInstance.addTable(defineTableLineItemExtrinsic());
|
||||||
|
qInstance.addTable(defineTableOrderExtrinsic());
|
||||||
|
|
||||||
qInstance.addJoin(defineJoinOrderLineItem());
|
qInstance.addJoin(defineJoinOrderLineItem());
|
||||||
|
qInstance.addJoin(defineJoinLineItemLineItemExtrinsic());
|
||||||
|
qInstance.addJoin(defineJoinOrderOrderExtrinsic());
|
||||||
|
|
||||||
qInstance.addPossibleValueSource(defineAutomationStatusPossibleValueSource());
|
qInstance.addPossibleValueSource(defineAutomationStatusPossibleValueSource());
|
||||||
qInstance.addPossibleValueSource(defineStatesPossibleValueSource());
|
qInstance.addPossibleValueSource(defineStatesPossibleValueSource());
|
||||||
@ -538,9 +545,12 @@ public class TestUtils
|
|||||||
.withRecordSecurityLock(new RecordSecurityLock()
|
.withRecordSecurityLock(new RecordSecurityLock()
|
||||||
.withSecurityKeyType(SECURITY_KEY_TYPE_STORE)
|
.withSecurityKeyType(SECURITY_KEY_TYPE_STORE)
|
||||||
.withFieldName("storeId"))
|
.withFieldName("storeId"))
|
||||||
|
.withAssociation(new Association().withName("orderLine").withAssociatedTableName(TABLE_NAME_LINE_ITEM).withJoinName("orderLineItem"))
|
||||||
|
.withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_ORDER_EXTRINSIC).withJoinName("orderOrderExtrinsic"))
|
||||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
|
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
|
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("orderNo", QFieldType.STRING))
|
||||||
.withField(new QFieldMetaData("orderDate", QFieldType.DATE))
|
.withField(new QFieldMetaData("orderDate", QFieldType.DATE))
|
||||||
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER))
|
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER))
|
||||||
.withField(new QFieldMetaData("total", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY).withFieldSecurityLock(new FieldSecurityLock()
|
.withField(new QFieldMetaData("total", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY).withFieldSecurityLock(new FieldSecurityLock()
|
||||||
@ -561,6 +571,7 @@ public class TestUtils
|
|||||||
.withName(TABLE_NAME_LINE_ITEM)
|
.withName(TABLE_NAME_LINE_ITEM)
|
||||||
.withBackendName(MEMORY_BACKEND_NAME)
|
.withBackendName(MEMORY_BACKEND_NAME)
|
||||||
.withPrimaryKeyField("id")
|
.withPrimaryKeyField("id")
|
||||||
|
.withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_LINE_ITEM_EXTRINSIC).withJoinName("lineItemLineItemExtrinsic"))
|
||||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
|
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
|
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
@ -572,6 +583,44 @@ public class TestUtils
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Define the lineItemExtrinsic table used in standard tests.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QTableMetaData defineTableLineItemExtrinsic()
|
||||||
|
{
|
||||||
|
return new QTableMetaData()
|
||||||
|
.withName(TABLE_NAME_LINE_ITEM_EXTRINSIC)
|
||||||
|
.withBackendName(MEMORY_BACKEND_NAME)
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("lineItemId", QFieldType.INTEGER))
|
||||||
|
.withField(new QFieldMetaData("key", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("value", QFieldType.STRING));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Define the orderExtrinsic table used in standard tests.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QTableMetaData defineTableOrderExtrinsic()
|
||||||
|
{
|
||||||
|
return new QTableMetaData()
|
||||||
|
.withName(TABLE_NAME_ORDER_EXTRINSIC)
|
||||||
|
.withBackendName(MEMORY_BACKEND_NAME)
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER))
|
||||||
|
.withField(new QFieldMetaData("key", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("value", QFieldType.STRING));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -588,6 +637,38 @@ public class TestUtils
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QJoinMetaData defineJoinLineItemLineItemExtrinsic()
|
||||||
|
{
|
||||||
|
return new QJoinMetaData()
|
||||||
|
.withName("lineItemLineItemExtrinsic")
|
||||||
|
.withType(JoinType.ONE_TO_MANY)
|
||||||
|
.withLeftTable(TABLE_NAME_LINE_ITEM)
|
||||||
|
.withRightTable(TABLE_NAME_LINE_ITEM_EXTRINSIC)
|
||||||
|
.withJoinOn(new JoinOn("id", "lineItemId"))
|
||||||
|
.withOrderBy(new QFilterOrderBy("key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QJoinMetaData defineJoinOrderOrderExtrinsic()
|
||||||
|
{
|
||||||
|
return new QJoinMetaData()
|
||||||
|
.withName("orderOrderExtrinsic")
|
||||||
|
.withType(JoinType.ONE_TO_MANY)
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER)
|
||||||
|
.withRightTable(TABLE_NAME_ORDER_EXTRINSIC)
|
||||||
|
.withJoinOn(new JoinOn("id", "orderId"))
|
||||||
|
.withOrderBy(new QFilterOrderBy("key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -26,6 +26,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import com.kingsrook.qqq.api.model.APIVersion;
|
import com.kingsrook.qqq.api.model.APIVersion;
|
||||||
import com.kingsrook.qqq.api.model.APIVersionRange;
|
import com.kingsrook.qqq.api.model.APIVersionRange;
|
||||||
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput;
|
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput;
|
||||||
@ -66,8 +67,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.YamlUtils;
|
import com.kingsrook.qqq.backend.core.utils.YamlUtils;
|
||||||
@ -204,7 +207,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
|||||||
boolean updateCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_UPDATE);
|
boolean updateCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_UPDATE);
|
||||||
boolean deleteCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_DELETE);
|
boolean deleteCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_DELETE);
|
||||||
boolean insertCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_INSERT);
|
boolean insertCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_INSERT);
|
||||||
boolean countCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_COUNT);
|
boolean countCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_COUNT); // todo - look at this - if table doesn't have count, don't include it in its input/output, etc
|
||||||
|
|
||||||
if(!queryCapability && !getCapability && !updateCapability && !deleteCapability && !insertCapability)
|
if(!queryCapability && !getCapability && !updateCapability && !deleteCapability && !insertCapability)
|
||||||
{
|
{
|
||||||
@ -221,6 +224,9 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
|||||||
String primaryKeyApiName = ApiFieldMetaData.getEffectiveApiFieldName(primaryKeyField);
|
String primaryKeyApiName = ApiFieldMetaData.getEffectiveApiFieldName(primaryKeyField);
|
||||||
List<QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
|
List<QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
// permissions for the table //
|
||||||
|
///////////////////////////////
|
||||||
String tableReadPermissionName = PermissionsHelper.getTablePermissionName(tableName, TablePermissionSubType.READ);
|
String tableReadPermissionName = PermissionsHelper.getTablePermissionName(tableName, TablePermissionSubType.READ);
|
||||||
if(StringUtils.hasContent(tableReadPermissionName))
|
if(StringUtils.hasContent(tableReadPermissionName))
|
||||||
{
|
{
|
||||||
@ -256,13 +262,15 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
|||||||
.withName(tableLabel)
|
.withName(tableLabel)
|
||||||
.withDescription("Operations on the " + tableLabel + " table."));
|
.withDescription("Operations on the " + tableLabel + " table."));
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
// build the schemas for this table //
|
// build the schemas for this table //
|
||||||
//////////////////////////////////////
|
// start with the full table minus its pkey (e.g., for posting) //
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
LinkedHashMap<String, Schema> tableFieldsWithoutPrimaryKey = new LinkedHashMap<>();
|
LinkedHashMap<String, Schema> tableFieldsWithoutPrimaryKey = new LinkedHashMap<>();
|
||||||
componentSchemas.put(tableApiName + "WithoutPrimaryKey", new Schema()
|
Schema tableWithoutPrimaryKeySchema = new Schema()
|
||||||
.withType("object")
|
.withType("object")
|
||||||
.withProperties(tableFieldsWithoutPrimaryKey));
|
.withProperties(tableFieldsWithoutPrimaryKey);
|
||||||
|
componentSchemas.put(tableApiName + "WithoutPrimaryKey", tableWithoutPrimaryKeySchema);
|
||||||
|
|
||||||
for(QFieldMetaData field : tableApiFields)
|
for(QFieldMetaData field : tableApiFields)
|
||||||
{
|
{
|
||||||
@ -271,46 +279,26 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
String apiFieldName = ApiFieldMetaData.getEffectiveApiFieldName(field);
|
Schema fieldSchema = getFieldSchema(table, field);
|
||||||
|
tableFieldsWithoutPrimaryKey.put(ApiFieldMetaData.getEffectiveApiFieldName(field), fieldSchema);
|
||||||
Schema fieldSchema = new Schema()
|
|
||||||
.withType(getFieldType(table.getField(field.getName())))
|
|
||||||
.withFormat(getFieldFormat(table.getField(field.getName())))
|
|
||||||
.withDescription(field.getLabel() + " for the " + tableLabel + ".");
|
|
||||||
|
|
||||||
if(StringUtils.hasContent(field.getPossibleValueSourceName()))
|
|
||||||
{
|
|
||||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
|
||||||
if(QPossibleValueSourceType.ENUM.equals(possibleValueSource.getType()))
|
|
||||||
{
|
|
||||||
List<String> enumValues = new ArrayList<>();
|
|
||||||
for(QPossibleValue<?> enumValue : possibleValueSource.getEnumValues())
|
|
||||||
{
|
|
||||||
enumValues.add(enumValue.getId() + "=" + enumValue.getLabel());
|
|
||||||
}
|
|
||||||
fieldSchema.setEnumValues(enumValues);
|
|
||||||
}
|
|
||||||
else if(QPossibleValueSourceType.TABLE.equals(possibleValueSource.getType()))
|
|
||||||
{
|
|
||||||
QTableMetaData sourceTable = qInstance.getTable(possibleValueSource.getTableName());
|
|
||||||
fieldSchema.setDescription(fieldSchema.getDescription() + " Values in this field come from the primary key of the " + sourceTable.getLabel() + " table");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tableFieldsWithoutPrimaryKey.put(apiFieldName, fieldSchema);
|
//////////////////////////////////
|
||||||
}
|
// recursively add associations //
|
||||||
|
//////////////////////////////////
|
||||||
|
addAssociations(table, tableWithoutPrimaryKeySchema);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// full version of table (w/o pkey + the pkey) //
|
||||||
|
/////////////////////////////////////////////////
|
||||||
componentSchemas.put(tableApiName, new Schema()
|
componentSchemas.put(tableApiName, new Schema()
|
||||||
.withType("object")
|
.withType("object")
|
||||||
.withAllOf(ListBuilder.of(new Schema().withRef("#/components/schemas/" + tableApiName + "WithoutPrimaryKey")))
|
.withAllOf(ListBuilder.of(new Schema().withRef("#/components/schemas/" + tableApiName + "WithoutPrimaryKey")))
|
||||||
.withProperties(MapBuilder.of(
|
.withProperties(MapBuilder.of(primaryKeyApiName, getFieldSchema(table, table.getField(primaryKeyName)))));
|
||||||
primaryKeyApiName, new Schema()
|
|
||||||
.withType(getFieldType(table.getField(primaryKeyName)))
|
|
||||||
.withFormat(getFieldFormat(table.getField(primaryKeyName)))
|
|
||||||
.withDescription(primaryKeyLabel + " for the " + tableLabel + ". Primary Key.")
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// table as a search result (the base search result, plus the table itself) //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
componentSchemas.put(tableApiName + "SearchResult", new Schema()
|
componentSchemas.put(tableApiName + "SearchResult", new Schema()
|
||||||
.withType("object")
|
.withType("object")
|
||||||
.withAllOf(ListBuilder.of(new Schema().withRef("#/components/schemas/baseSearchResultFields")))
|
.withAllOf(ListBuilder.of(new Schema().withRef("#/components/schemas/baseSearchResultFields")))
|
||||||
@ -577,6 +565,59 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static void addAssociations(QTableMetaData table, Schema tableWithoutPrimaryKeySchema)
|
||||||
|
{
|
||||||
|
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||||
|
{
|
||||||
|
String associatedTableName = association.getAssociatedTableName();
|
||||||
|
QTableMetaData associatedTable = QContext.getQInstance().getTable(associatedTableName);
|
||||||
|
ApiTableMetaData associatedApiTableMetaData = Objects.requireNonNullElse(ApiTableMetaData.of(associatedTable), new ApiTableMetaData());
|
||||||
|
String associatedTableApiName = StringUtils.hasContent(associatedApiTableMetaData.getApiTableName()) ? associatedApiTableMetaData.getApiTableName() : associatedTableName;
|
||||||
|
|
||||||
|
tableWithoutPrimaryKeySchema.getProperties().put(association.getName(), new Schema()
|
||||||
|
.withType("array")
|
||||||
|
.withItems(new Schema().withRef("#/components/schemas/" + associatedTableApiName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private Schema getFieldSchema(QTableMetaData table, QFieldMetaData field)
|
||||||
|
{
|
||||||
|
Schema fieldSchema = new Schema()
|
||||||
|
.withType(getFieldType(table.getField(field.getName())))
|
||||||
|
.withFormat(getFieldFormat(table.getField(field.getName())))
|
||||||
|
.withDescription(field.getLabel() + " for the " + table.getLabel() + ".");
|
||||||
|
|
||||||
|
if(StringUtils.hasContent(field.getPossibleValueSourceName()))
|
||||||
|
{
|
||||||
|
QPossibleValueSource possibleValueSource = QContext.getQInstance().getPossibleValueSource(field.getPossibleValueSourceName());
|
||||||
|
if(QPossibleValueSourceType.ENUM.equals(possibleValueSource.getType()))
|
||||||
|
{
|
||||||
|
List<String> enumValues = new ArrayList<>();
|
||||||
|
for(QPossibleValue<?> enumValue : possibleValueSource.getEnumValues())
|
||||||
|
{
|
||||||
|
enumValues.add(enumValue.getId() + "=" + enumValue.getLabel());
|
||||||
|
}
|
||||||
|
fieldSchema.setEnumValues(enumValues);
|
||||||
|
}
|
||||||
|
else if(QPossibleValueSourceType.TABLE.equals(possibleValueSource.getType()))
|
||||||
|
{
|
||||||
|
QTableMetaData sourceTable = QContext.getQInstance().getTable(possibleValueSource.getTableName());
|
||||||
|
fieldSchema.setDescription(fieldSchema.getDescription() + " Values in this field come from the primary key of the " + sourceTable.getLabel() + " table");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fieldSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -32,11 +32,16 @@ import java.util.stream.Collectors;
|
|||||||
import com.kingsrook.qqq.api.javalin.QBadRequestException;
|
import com.kingsrook.qqq.api.javalin.QBadRequestException;
|
||||||
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
|
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
|
||||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
|
||||||
@ -94,6 +99,13 @@ public class QRecordApiAdapter
|
|||||||
List<String> unrecognizedFieldNames = new ArrayList<>();
|
List<String> unrecognizedFieldNames = new ArrayList<>();
|
||||||
QRecord qRecord = new QRecord();
|
QRecord qRecord = new QRecord();
|
||||||
|
|
||||||
|
Map<String, Association> associationMap = new HashMap<>();
|
||||||
|
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||||
|
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||||
|
{
|
||||||
|
associationMap.put(association.getName(), association);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////
|
//////////////////////////////////////////
|
||||||
// iterate over keys in the json object //
|
// iterate over keys in the json object //
|
||||||
//////////////////////////////////////////
|
//////////////////////////////////////////
|
||||||
@ -117,6 +129,30 @@ public class QRecordApiAdapter
|
|||||||
qRecord.setValue(field.getName(), value);
|
qRecord.setValue(field.getName(), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(associationMap.containsKey(jsonKey))
|
||||||
|
{
|
||||||
|
Association association = associationMap.get(jsonKey);
|
||||||
|
Object value = jsonObject.get(jsonKey);
|
||||||
|
if(value instanceof JSONArray jsonArray)
|
||||||
|
{
|
||||||
|
for(Object subObject : jsonArray)
|
||||||
|
{
|
||||||
|
if(subObject instanceof JSONObject subJsonObject)
|
||||||
|
{
|
||||||
|
QRecord subRecord = apiJsonObjectToQRecord(subJsonObject, association.getAssociatedTableName(), apiVersion);
|
||||||
|
qRecord.withAssociatedRecord(association.getName(), subRecord);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("Found a " + value.getClass().getSimpleName() + " in the array under key " + jsonKey + ", but a JSON object is required here."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("Found a " + value.getClass().getSimpleName() + " at key " + jsonKey + ", but a JSON array is required here."));
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
///////////////////////////////////////////////////
|
///////////////////////////////////////////////////
|
||||||
|
@ -76,6 +76,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||||
@ -253,7 +254,7 @@ public class QJavalinApiHandler
|
|||||||
String version = context.pathParam("version");
|
String version = context.pathParam("version");
|
||||||
GenerateOpenApiSpecInput input = new GenerateOpenApiSpecInput().withVersion(version);
|
GenerateOpenApiSpecInput input = new GenerateOpenApiSpecInput().withVersion(version);
|
||||||
|
|
||||||
if(context.pathParam("tableName") != null)
|
if(StringUtils.hasContent(context.pathParam("tableName")))
|
||||||
{
|
{
|
||||||
input.setTableName(context.pathParam("tableName"));
|
input.setTableName(context.pathParam("tableName"));
|
||||||
}
|
}
|
||||||
@ -273,9 +274,10 @@ public class QJavalinApiHandler
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static void setupSession(Context context, AbstractActionInput input) throws QModuleDispatchException, QAuthenticationException
|
public static void setupSession(Context context, AbstractActionInput input, String version) throws QModuleDispatchException, QAuthenticationException
|
||||||
{
|
{
|
||||||
QJavalinImplementation.setupSession(context, input);
|
QSession session = QJavalinImplementation.setupSession(context, input);
|
||||||
|
session.setValue("apiVersion", version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -296,8 +298,8 @@ public class QJavalinApiHandler
|
|||||||
|
|
||||||
GetInput getInput = new GetInput();
|
GetInput getInput = new GetInput();
|
||||||
|
|
||||||
setupSession(context, getInput);
|
setupSession(context, getInput, version);
|
||||||
QJavalinAccessLogger.logStart("get", logPair("table", tableName), logPair("primaryKey", primaryKey));
|
QJavalinAccessLogger.logStart("apiGet", logPair("table", tableName), logPair("primaryKey", primaryKey));
|
||||||
|
|
||||||
getInput.setTableName(tableName);
|
getInput.setTableName(tableName);
|
||||||
// i think not for api... getInput.setShouldGenerateDisplayValues(true);
|
// i think not for api... getInput.setShouldGenerateDisplayValues(true);
|
||||||
@ -354,7 +356,7 @@ public class QJavalinApiHandler
|
|||||||
String tableName = table.getName();
|
String tableName = table.getName();
|
||||||
|
|
||||||
QueryInput queryInput = new QueryInput();
|
QueryInput queryInput = new QueryInput();
|
||||||
setupSession(context, queryInput);
|
setupSession(context, queryInput, version);
|
||||||
QJavalinAccessLogger.logStart("apiQuery", logPair("table", tableName));
|
QJavalinAccessLogger.logStart("apiQuery", logPair("table", tableName));
|
||||||
|
|
||||||
queryInput.setTableName(tableName);
|
queryInput.setTableName(tableName);
|
||||||
@ -785,8 +787,8 @@ public class QJavalinApiHandler
|
|||||||
|
|
||||||
InsertInput insertInput = new InsertInput();
|
InsertInput insertInput = new InsertInput();
|
||||||
|
|
||||||
setupSession(context, insertInput);
|
setupSession(context, insertInput, version);
|
||||||
QJavalinAccessLogger.logStart("insert", logPair("table", tableName));
|
QJavalinAccessLogger.logStart("apiInsert", logPair("table", tableName));
|
||||||
|
|
||||||
insertInput.setTableName(tableName);
|
insertInput.setTableName(tableName);
|
||||||
|
|
||||||
@ -852,8 +854,8 @@ public class QJavalinApiHandler
|
|||||||
|
|
||||||
InsertInput insertInput = new InsertInput();
|
InsertInput insertInput = new InsertInput();
|
||||||
|
|
||||||
setupSession(context, insertInput);
|
setupSession(context, insertInput, version);
|
||||||
QJavalinAccessLogger.logStart("bulkInsert", logPair("table", tableName));
|
QJavalinAccessLogger.logStart("apiBulkInsert", logPair("table", tableName));
|
||||||
|
|
||||||
insertInput.setTableName(tableName);
|
insertInput.setTableName(tableName);
|
||||||
|
|
||||||
@ -958,8 +960,8 @@ public class QJavalinApiHandler
|
|||||||
|
|
||||||
UpdateInput updateInput = new UpdateInput();
|
UpdateInput updateInput = new UpdateInput();
|
||||||
|
|
||||||
setupSession(context, updateInput);
|
setupSession(context, updateInput, version);
|
||||||
QJavalinAccessLogger.logStart("bulkUpdate", logPair("table", tableName));
|
QJavalinAccessLogger.logStart("apiBulkUpdate", logPair("table", tableName));
|
||||||
|
|
||||||
updateInput.setTableName(tableName);
|
updateInput.setTableName(tableName);
|
||||||
|
|
||||||
@ -1063,8 +1065,8 @@ public class QJavalinApiHandler
|
|||||||
|
|
||||||
DeleteInput deleteInput = new DeleteInput();
|
DeleteInput deleteInput = new DeleteInput();
|
||||||
|
|
||||||
setupSession(context, deleteInput);
|
setupSession(context, deleteInput, version);
|
||||||
QJavalinAccessLogger.logStart("bulkDelete", logPair("table", tableName));
|
QJavalinAccessLogger.logStart("apiBulkDelete", logPair("table", tableName));
|
||||||
|
|
||||||
deleteInput.setTableName(tableName);
|
deleteInput.setTableName(tableName);
|
||||||
|
|
||||||
@ -1182,8 +1184,8 @@ public class QJavalinApiHandler
|
|||||||
|
|
||||||
UpdateInput updateInput = new UpdateInput();
|
UpdateInput updateInput = new UpdateInput();
|
||||||
|
|
||||||
setupSession(context, updateInput);
|
setupSession(context, updateInput, version);
|
||||||
QJavalinAccessLogger.logStart("update", logPair("table", tableName));
|
QJavalinAccessLogger.logStart("apiUpdate", logPair("table", tableName));
|
||||||
|
|
||||||
updateInput.setTableName(tableName);
|
updateInput.setTableName(tableName);
|
||||||
|
|
||||||
@ -1268,8 +1270,8 @@ public class QJavalinApiHandler
|
|||||||
|
|
||||||
DeleteInput deleteInput = new DeleteInput();
|
DeleteInput deleteInput = new DeleteInput();
|
||||||
|
|
||||||
setupSession(context, deleteInput);
|
setupSession(context, deleteInput, version);
|
||||||
QJavalinAccessLogger.logStart("delete", logPair("table", tableName));
|
QJavalinAccessLogger.logStart("apiDelete", logPair("table", tableName));
|
||||||
|
|
||||||
deleteInput.setTableName(tableName);
|
deleteInput.setTableName(tableName);
|
||||||
deleteInput.setPrimaryKeys(List.of(primaryKey));
|
deleteInput.setPrimaryKeys(List.of(primaryKey));
|
||||||
|
@ -27,6 +27,7 @@ import com.kingsrook.qqq.api.model.APIVersion;
|
|||||||
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
|
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
|
||||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
||||||
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
@ -34,6 +35,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0Authent
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
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.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
||||||
@ -45,10 +50,15 @@ import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.Mem
|
|||||||
public class TestUtils
|
public class TestUtils
|
||||||
{
|
{
|
||||||
public static final String MEMORY_BACKEND_NAME = "memory";
|
public static final String MEMORY_BACKEND_NAME = "memory";
|
||||||
public static final String TABLE_NAME_PERSON = "person";
|
|
||||||
|
|
||||||
public static final String V2023_Q1 = "2023.Q1";
|
public static final String TABLE_NAME_PERSON = "person";
|
||||||
|
public static final String TABLE_NAME_ORDER = "order";
|
||||||
|
public static final String TABLE_NAME_LINE_ITEM = "orderLine";
|
||||||
|
public static final String TABLE_NAME_LINE_ITEM_EXTRINSIC = "orderLineExtrinsic";
|
||||||
|
public static final String TABLE_NAME_ORDER_EXTRINSIC = "orderExtrinsic";
|
||||||
|
|
||||||
public static final String V2022_Q4 = "2022.Q4";
|
public static final String V2022_Q4 = "2022.Q4";
|
||||||
|
public static final String V2023_Q1 = "2023.Q1";
|
||||||
public static final String V2023_Q2 = "2023.Q2";
|
public static final String V2023_Q2 = "2023.Q2";
|
||||||
|
|
||||||
public static final String CURRENT_API_VERSION = V2023_Q1;
|
public static final String CURRENT_API_VERSION = V2023_Q1;
|
||||||
@ -64,6 +74,15 @@ public class TestUtils
|
|||||||
|
|
||||||
qInstance.addBackend(defineMemoryBackend());
|
qInstance.addBackend(defineMemoryBackend());
|
||||||
qInstance.addTable(defineTablePerson());
|
qInstance.addTable(defineTablePerson());
|
||||||
|
qInstance.addTable(defineTableOrder());
|
||||||
|
qInstance.addTable(defineTableLineItem());
|
||||||
|
qInstance.addTable(defineTableLineItemExtrinsic());
|
||||||
|
qInstance.addTable(defineTableOrderExtrinsic());
|
||||||
|
|
||||||
|
qInstance.addJoin(defineJoinOrderLineItem());
|
||||||
|
qInstance.addJoin(defineJoinLineItemLineItemExtrinsic());
|
||||||
|
qInstance.addJoin(defineJoinOrderOrderExtrinsic());
|
||||||
|
|
||||||
qInstance.setAuthentication(new Auth0AuthenticationMetaData().withType(QAuthenticationType.FULLY_ANONYMOUS).withName("anonymous"));
|
qInstance.setAuthentication(new Auth0AuthenticationMetaData().withType(QAuthenticationType.FULLY_ANONYMOUS).withName("anonymous"));
|
||||||
|
|
||||||
qInstance.withMiddlewareMetaData(new ApiInstanceMetaData()
|
qInstance.withMiddlewareMetaData(new ApiInstanceMetaData()
|
||||||
@ -139,4 +158,137 @@ public class TestUtils
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Define the order table used in standard tests.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QTableMetaData defineTableOrder()
|
||||||
|
{
|
||||||
|
return new QTableMetaData()
|
||||||
|
.withName(TABLE_NAME_ORDER)
|
||||||
|
.withBackendName(MEMORY_BACKEND_NAME)
|
||||||
|
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(V2022_Q4))
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withAssociation(new Association().withName("orderLines").withAssociatedTableName(TABLE_NAME_LINE_ITEM).withJoinName("orderLineItem"))
|
||||||
|
.withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_ORDER_EXTRINSIC).withJoinName("orderOrderExtrinsic"))
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("orderNo", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("orderDate", QFieldType.DATE))
|
||||||
|
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER))
|
||||||
|
.withField(new QFieldMetaData("total", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Define the lineItem table used in standard tests.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QTableMetaData defineTableLineItem()
|
||||||
|
{
|
||||||
|
return new QTableMetaData()
|
||||||
|
.withName(TABLE_NAME_LINE_ITEM)
|
||||||
|
.withBackendName(MEMORY_BACKEND_NAME)
|
||||||
|
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(V2022_Q4))
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_LINE_ITEM_EXTRINSIC).withJoinName("lineItemLineItemExtrinsic"))
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER))
|
||||||
|
.withField(new QFieldMetaData("lineNumber", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("sku", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("quantity", QFieldType.INTEGER));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Define the lineItemExtrinsic table used in standard tests.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QTableMetaData defineTableLineItemExtrinsic()
|
||||||
|
{
|
||||||
|
return new QTableMetaData()
|
||||||
|
.withName(TABLE_NAME_LINE_ITEM_EXTRINSIC)
|
||||||
|
.withBackendName(MEMORY_BACKEND_NAME)
|
||||||
|
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(V2022_Q4))
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("lineItemId", QFieldType.INTEGER))
|
||||||
|
.withField(new QFieldMetaData("key", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("value", QFieldType.STRING));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Define the orderExtrinsic table used in standard tests.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QTableMetaData defineTableOrderExtrinsic()
|
||||||
|
{
|
||||||
|
return new QTableMetaData()
|
||||||
|
.withName(TABLE_NAME_ORDER_EXTRINSIC)
|
||||||
|
.withBackendName(MEMORY_BACKEND_NAME)
|
||||||
|
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(V2022_Q4))
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER))
|
||||||
|
.withField(new QFieldMetaData("key", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("value", QFieldType.STRING));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QJoinMetaData defineJoinOrderLineItem()
|
||||||
|
{
|
||||||
|
return new QJoinMetaData()
|
||||||
|
.withName("orderLineItem")
|
||||||
|
.withType(JoinType.ONE_TO_MANY)
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER)
|
||||||
|
.withRightTable(TABLE_NAME_LINE_ITEM)
|
||||||
|
.withJoinOn(new JoinOn("id", "orderId"))
|
||||||
|
.withOrderBy(new QFilterOrderBy("lineNumber"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QJoinMetaData defineJoinLineItemLineItemExtrinsic()
|
||||||
|
{
|
||||||
|
return new QJoinMetaData()
|
||||||
|
.withName("lineItemLineItemExtrinsic")
|
||||||
|
.withType(JoinType.ONE_TO_MANY)
|
||||||
|
.withLeftTable(TABLE_NAME_LINE_ITEM)
|
||||||
|
.withRightTable(TABLE_NAME_LINE_ITEM_EXTRINSIC)
|
||||||
|
.withJoinOn(new JoinOn("id", "lineItemId"))
|
||||||
|
.withOrderBy(new QFilterOrderBy("key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QJoinMetaData defineJoinOrderOrderExtrinsic()
|
||||||
|
{
|
||||||
|
return new QJoinMetaData()
|
||||||
|
.withName("orderOrderExtrinsic")
|
||||||
|
.withType(JoinType.ONE_TO_MANY)
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER)
|
||||||
|
.withRightTable(TABLE_NAME_ORDER_EXTRINSIC)
|
||||||
|
.withJoinOn(new JoinOn("id", "orderId"))
|
||||||
|
.withOrderBy(new QFilterOrderBy("key"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
@ -59,6 +60,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -407,12 +409,55 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||||
assertEquals(1, jsonObject.getInt("id"));
|
assertEquals(1, jsonObject.getInt("id"));
|
||||||
|
|
||||||
QRecord record = getPersonRecord(1);
|
QRecord record = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
|
||||||
assertEquals("Moe", record.getValueString("firstName"));
|
assertEquals("Moe", record.getValueString("firstName"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testInsert201WithAssociatedRecords() throws QException
|
||||||
|
{
|
||||||
|
HttpResponse<String> response = Unirest.post(BASE_URL + "/api/" + VERSION + "/order/")
|
||||||
|
.body("""
|
||||||
|
{"orderNo": "ORD123", "storeId": 47, "orderLines":
|
||||||
|
[
|
||||||
|
{"lineNumber": 1, "sku": "BASIC1", "quantity": 17, "extrinsics": [{"key": "size", "value": "Large"}]},
|
||||||
|
{"lineNumber": 2, "sku": "BASIC2", "quantity": 23}
|
||||||
|
], "extrinsics":
|
||||||
|
[
|
||||||
|
{"key": "storeName", "value": "My Shopify"},
|
||||||
|
{"key": "shopifyOrderNo", "value": "#2820503"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
.asString();
|
||||||
|
System.out.println(response.getBody());
|
||||||
|
assertEquals(HttpStatus.CREATED_201, response.getStatus());
|
||||||
|
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||||
|
assertEquals(1, jsonObject.getInt("id"));
|
||||||
|
|
||||||
|
QRecord record = getRecord(TestUtils.TABLE_NAME_ORDER, 1);
|
||||||
|
assertEquals("ORD123", record.getValueString("orderNo"));
|
||||||
|
|
||||||
|
List<QRecord> lines = queryTable(TestUtils.TABLE_NAME_LINE_ITEM);
|
||||||
|
assertEquals(2, lines.size());
|
||||||
|
assertTrue(lines.stream().allMatch(r -> r.getValueInteger("orderId").equals(1)));
|
||||||
|
assertTrue(lines.stream().anyMatch(r -> r.getValueString("sku").equals("BASIC1")));
|
||||||
|
assertTrue(lines.stream().anyMatch(r -> r.getValueString("sku").equals("BASIC2")));
|
||||||
|
|
||||||
|
List<QRecord> orderExtrinsics = queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
|
||||||
|
assertEquals(2, orderExtrinsics.size());
|
||||||
|
assertTrue(orderExtrinsics.stream().allMatch(r -> r.getValueInteger("orderId").equals(1)));
|
||||||
|
assertTrue(orderExtrinsics.stream().anyMatch(r -> r.getValueString("key").equals("storeName") && r.getValueString("value").equals("My Shopify")));
|
||||||
|
assertTrue(orderExtrinsics.stream().anyMatch(r -> r.getValueString("key").equals("shopifyOrderNo") && r.getValueString("value").equals("#2820503")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -462,7 +507,7 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
// assert it didn't get inserted //
|
// assert it didn't get inserted //
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
QRecord personRecord = getPersonRecord(1);
|
QRecord personRecord = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
|
||||||
assertNull(personRecord);
|
assertNull(personRecord);
|
||||||
|
|
||||||
///////////////////////////////////////////
|
///////////////////////////////////////////
|
||||||
@ -511,16 +556,16 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(3).getInt("statusCode"));
|
assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(3).getInt("statusCode"));
|
||||||
assertEquals("Error inserting Person: Another record already exists with this Email", jsonArray.getJSONObject(3).getString("error"));
|
assertEquals("Error inserting Person: Another record already exists with this Email", jsonArray.getJSONObject(3).getString("error"));
|
||||||
|
|
||||||
QRecord record = getPersonRecord(1);
|
QRecord record = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
|
||||||
assertEquals("Moe", record.getValueString("firstName"));
|
assertEquals("Moe", record.getValueString("firstName"));
|
||||||
|
|
||||||
record = getPersonRecord(2);
|
record = getRecord(TestUtils.TABLE_NAME_PERSON, 2);
|
||||||
assertEquals("Barney", record.getValueString("firstName"));
|
assertEquals("Barney", record.getValueString("firstName"));
|
||||||
|
|
||||||
record = getPersonRecord(3);
|
record = getRecord(TestUtils.TABLE_NAME_PERSON, 3);
|
||||||
assertEquals("CM", record.getValueString("firstName"));
|
assertEquals("CM", record.getValueString("firstName"));
|
||||||
|
|
||||||
record = getPersonRecord(4);
|
record = getRecord(TestUtils.TABLE_NAME_PERSON, 4);
|
||||||
assertNull(record);
|
assertNull(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,7 +604,7 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
/////////////////////////////////
|
/////////////////////////////////
|
||||||
// assert nothing got inserted //
|
// assert nothing got inserted //
|
||||||
/////////////////////////////////
|
/////////////////////////////////
|
||||||
QRecord personRecord = getPersonRecord(1);
|
QRecord personRecord = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
|
||||||
assertNull(personRecord);
|
assertNull(personRecord);
|
||||||
|
|
||||||
//////////////////////////////////////////
|
//////////////////////////////////////////
|
||||||
@ -592,7 +637,7 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
|
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
|
||||||
assertFalse(StringUtils.hasContent(response.getBody()));
|
assertFalse(StringUtils.hasContent(response.getBody()));
|
||||||
|
|
||||||
QRecord record = getPersonRecord(1);
|
QRecord record = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
|
||||||
assertEquals("Charles", record.getValueString("firstName"));
|
assertEquals("Charles", record.getValueString("firstName"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -665,7 +710,7 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
// assert it didn't get updated. //
|
// assert it didn't get updated. //
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
QRecord personRecord = getPersonRecord(1);
|
QRecord personRecord = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
|
||||||
assertEquals("Mo", personRecord.getValueString("firstName"));
|
assertEquals("Mo", personRecord.getValueString("firstName"));
|
||||||
|
|
||||||
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||||
@ -706,10 +751,10 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(2).getInt("statusCode"));
|
assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(2).getInt("statusCode"));
|
||||||
assertEquals("Error updating Person: Missing value in primary key field", jsonArray.getJSONObject(2).getString("error"));
|
assertEquals("Error updating Person: Missing value in primary key field", jsonArray.getJSONObject(2).getString("error"));
|
||||||
|
|
||||||
QRecord record = getPersonRecord(1);
|
QRecord record = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
|
||||||
assertEquals("homer@simpson.com", record.getValueString("email"));
|
assertEquals("homer@simpson.com", record.getValueString("email"));
|
||||||
|
|
||||||
record = getPersonRecord(2);
|
record = getRecord(TestUtils.TABLE_NAME_PERSON, 2);
|
||||||
assertEquals("marge@simpson.com", record.getValueString("email"));
|
assertEquals("marge@simpson.com", record.getValueString("email"));
|
||||||
|
|
||||||
QueryInput queryInput = new QueryInput();
|
QueryInput queryInput = new QueryInput();
|
||||||
@ -754,7 +799,7 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
// assert nothing got updated //
|
// assert nothing got updated //
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
QRecord personRecord = getPersonRecord(1);
|
QRecord personRecord = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
|
||||||
assertNull(personRecord);
|
assertNull(personRecord);
|
||||||
|
|
||||||
//////////////////////////////////////////
|
//////////////////////////////////////////
|
||||||
@ -834,7 +879,7 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
// assert nothing got deleted //
|
// assert nothing got deleted //
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
QRecord personRecord = getPersonRecord(1);
|
QRecord personRecord = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
|
||||||
assertNull(personRecord);
|
assertNull(personRecord);
|
||||||
|
|
||||||
//////////////////////////////////////////
|
//////////////////////////////////////////
|
||||||
@ -877,7 +922,7 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
|
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
|
||||||
assertFalse(StringUtils.hasContent(response.getBody()));
|
assertFalse(StringUtils.hasContent(response.getBody()));
|
||||||
|
|
||||||
QRecord record = getPersonRecord(1);
|
QRecord record = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
|
||||||
assertNull(record);
|
assertNull(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -895,6 +940,43 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testDeleteAssociations() throws QException
|
||||||
|
{
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
insertInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("orderNo", "ORD123").withValue("storeId", 47)
|
||||||
|
.withAssociatedRecord("orderLines", new QRecord().withValue("lineNumber", 1).withValue("sku", "BASIC1").withValue("quantity", 42)
|
||||||
|
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "Size").withValue("value", "Medium"))
|
||||||
|
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "Discount").withValue("value", "3.50"))
|
||||||
|
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "Color").withValue("value", "Red")))
|
||||||
|
.withAssociatedRecord("orderLines", new QRecord().withValue("lineNumber", 2).withValue("sku", "BASIC2").withValue("quantity", 42)
|
||||||
|
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "Size").withValue("value", "Medium")))
|
||||||
|
.withAssociatedRecord("orderLines", new QRecord().withValue("lineNumber", 3).withValue("sku", "BASIC3").withValue("quantity", 42))
|
||||||
|
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "shopifyOrderNo").withValue("value", "#1032"))
|
||||||
|
));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
|
||||||
|
assertEquals(1, queryTable(TestUtils.TABLE_NAME_ORDER).size());
|
||||||
|
assertEquals(4, queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC).size());
|
||||||
|
assertEquals(3, queryTable(TestUtils.TABLE_NAME_LINE_ITEM).size());
|
||||||
|
assertEquals(1, queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC).size());
|
||||||
|
|
||||||
|
HttpResponse<String> response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/order/1").asString();
|
||||||
|
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
|
||||||
|
assertFalse(StringUtils.hasContent(response.getBody()));
|
||||||
|
|
||||||
|
assertEquals(0, queryTable(TestUtils.TABLE_NAME_ORDER).size());
|
||||||
|
assertEquals(0, queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC).size());
|
||||||
|
assertEquals(0, queryTable(TestUtils.TABLE_NAME_LINE_ITEM).size());
|
||||||
|
assertEquals(0, queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -919,10 +1001,10 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private static QRecord getPersonRecord(Integer id) throws QException
|
private static QRecord getRecord(String tableName, Integer id) throws QException
|
||||||
{
|
{
|
||||||
GetInput getInput = new GetInput();
|
GetInput getInput = new GetInput();
|
||||||
getInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
getInput.setTableName(tableName);
|
||||||
getInput.setPrimaryKey(id);
|
getInput.setPrimaryKey(id);
|
||||||
GetOutput getOutput = new GetAction().execute(getInput);
|
GetOutput getOutput = new GetAction().execute(getInput);
|
||||||
QRecord record = getOutput.getRecord();
|
QRecord record = getOutput.getRecord();
|
||||||
@ -931,6 +1013,20 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static List<QRecord> queryTable(String tableName) throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(tableName);
|
||||||
|
queryInput.setFilter(new QQueryFilter().withOrderBy(new QFilterOrderBy("id")));
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
return (queryOutput.getRecords());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -411,7 +411,7 @@ public class QJavalinImplementation
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static void setupSession(Context context, AbstractActionInput input) throws QModuleDispatchException, QAuthenticationException
|
public static QSession setupSession(Context context, AbstractActionInput input) throws QModuleDispatchException, QAuthenticationException
|
||||||
{
|
{
|
||||||
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
||||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication());
|
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication());
|
||||||
@ -466,6 +466,8 @@ public class QJavalinImplementation
|
|||||||
|
|
||||||
setUserTimezoneOffsetMinutesInSession(context, session);
|
setUserTimezoneOffsetMinutesInSession(context, session);
|
||||||
setUserTimezoneInSession(context, session);
|
setUserTimezoneInSession(context, session);
|
||||||
|
|
||||||
|
return (session);
|
||||||
}
|
}
|
||||||
catch(QAuthenticationException qae)
|
catch(QAuthenticationException qae)
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user