mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Add support for variants to memory backend
This commit is contained in:
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantSetting;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** since some settings are required for a variant, if you're using memory backend
|
||||
** with variants, this is a setting you can use.
|
||||
*******************************************************************************/
|
||||
public enum MemoryModuleBackendVariantSetting implements BackendVariantSetting
|
||||
{
|
||||
PRIMARY_KEY
|
||||
}
|
@ -63,12 +63,15 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
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.fields.QFieldMetaData;
|
||||
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.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantsConfig;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantsUtil;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
@ -85,8 +88,11 @@ public class MemoryRecordStore
|
||||
|
||||
private static MemoryRecordStore instance;
|
||||
|
||||
private Map<String, Map<Serializable, QRecord>> data;
|
||||
private Map<String, Integer> nextSerials;
|
||||
//////////////////////////////////////////////////////////
|
||||
// these maps are: BackendIdentifier > tableName > data //
|
||||
//////////////////////////////////////////////////////////
|
||||
private Map<BackendIdentifier, Map<String, Map<Serializable, QRecord>>> data;
|
||||
private Map<BackendIdentifier, Map<String, Integer>> nextSerials;
|
||||
|
||||
private static boolean collectStatistics = false;
|
||||
|
||||
@ -150,13 +156,30 @@ public class MemoryRecordStore
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Map<Serializable, QRecord> getTableData(QTableMetaData table)
|
||||
private Map<Serializable, QRecord> getTableData(QTableMetaData table) throws QException
|
||||
{
|
||||
if(!data.containsKey(table.getName()))
|
||||
{
|
||||
data.put(table.getName(), new HashMap<>());
|
||||
BackendIdentifier backendIdentifier = getBackendIdentifier(table);
|
||||
Map<String, Map<Serializable, QRecord>> dataForBackend = data.computeIfAbsent(backendIdentifier, k -> new HashMap<>());
|
||||
return (dataForBackend.computeIfAbsent(table.getName(), k -> new HashMap<>()));
|
||||
}
|
||||
return (data.get(table.getName()));
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private BackendIdentifier getBackendIdentifier(QTableMetaData table) throws QException
|
||||
{
|
||||
BackendIdentifier backendIdentifier = NonVariant.getInstance();
|
||||
QBackendMetaData backendMetaData = QContext.getQInstance().getBackend(table.getBackendName());
|
||||
BackendVariantsConfig backendVariantsConfig = backendMetaData.getBackendVariantsConfig();
|
||||
if(backendVariantsConfig != null)
|
||||
{
|
||||
String variantType = backendMetaData.getBackendVariantsConfig().getVariantTypeKey();
|
||||
Serializable variantId = BackendVariantsUtil.getVariantId(backendMetaData);
|
||||
backendIdentifier = new Variant(variantType, variantId);
|
||||
}
|
||||
return backendIdentifier;
|
||||
}
|
||||
|
||||
|
||||
@ -329,7 +352,7 @@ public class MemoryRecordStore
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<QRecord> insert(InsertInput input, boolean returnInsertedRecords)
|
||||
public List<QRecord> insert(InsertInput input, boolean returnInsertedRecords) throws QException
|
||||
{
|
||||
incrementStatistic(input);
|
||||
|
||||
@ -344,7 +367,7 @@ public class MemoryRecordStore
|
||||
////////////////////////////////////////
|
||||
// grab the next unique serial to use //
|
||||
////////////////////////////////////////
|
||||
Integer nextSerial = nextSerials.get(table.getName());
|
||||
Integer nextSerial = getNextSerial(table);
|
||||
if(nextSerial == null)
|
||||
{
|
||||
nextSerial = 1;
|
||||
@ -407,17 +430,41 @@ public class MemoryRecordStore
|
||||
}
|
||||
}
|
||||
|
||||
nextSerials.put(table.getName(), nextSerial);
|
||||
setNextSerial(table, nextSerial);
|
||||
|
||||
return (outputRecords);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void setNextSerial(QTableMetaData table, Integer nextSerial) throws QException
|
||||
{
|
||||
BackendIdentifier backendIdentifier = getBackendIdentifier(table);
|
||||
Map<String, Integer> nextSerialsForBackend = nextSerials.computeIfAbsent(backendIdentifier, (k) -> new HashMap<>());
|
||||
nextSerialsForBackend.put(table.getName(), nextSerial);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private Integer getNextSerial(QTableMetaData table) throws QException
|
||||
{
|
||||
BackendIdentifier backendIdentifier = getBackendIdentifier(table);
|
||||
Map<String, Integer> nextSerialsForBackend = nextSerials.computeIfAbsent(backendIdentifier, (k) -> new HashMap<>());
|
||||
return (nextSerialsForBackend.get(table.getName()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<QRecord> update(UpdateInput input, boolean returnUpdatedRecords)
|
||||
public List<QRecord> update(UpdateInput input, boolean returnUpdatedRecords) throws QException
|
||||
{
|
||||
if(input.getRecords() == null)
|
||||
{
|
||||
@ -462,7 +509,7 @@ public class MemoryRecordStore
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public int delete(DeleteInput input)
|
||||
public int delete(DeleteInput input) throws QException
|
||||
{
|
||||
if(input.getPrimaryKeys() == null)
|
||||
{
|
||||
@ -927,4 +974,58 @@ public class MemoryRecordStore
|
||||
return (filter.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** key for the internal maps of this class - either for a non-variant version
|
||||
** of the memory backend, or for one based on variants.
|
||||
***************************************************************************/
|
||||
private sealed interface BackendIdentifier permits NonVariant, Variant
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** singleton, representing non-variant instance of memory backend.
|
||||
***************************************************************************/
|
||||
private static final class NonVariant implements BackendIdentifier
|
||||
{
|
||||
private static NonVariant nonVariant = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton constructor
|
||||
*******************************************************************************/
|
||||
private NonVariant()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton accessor
|
||||
*******************************************************************************/
|
||||
public static NonVariant getInstance()
|
||||
{
|
||||
if(nonVariant == null)
|
||||
{
|
||||
nonVariant = new NonVariant();
|
||||
}
|
||||
return (nonVariant);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** record representing a variant type & id
|
||||
***************************************************************************/
|
||||
private record Variant(String type, Serializable id) implements BackendIdentifier
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostQueryCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
@ -205,6 +206,60 @@ class MemoryBackendModuleTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testVariants() throws QException
|
||||
{
|
||||
////////////////////////////////////
|
||||
// insert our two variant options //
|
||||
////////////////////////////////////
|
||||
new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_MEMORY_VARIANT_OPTIONS).withRecords(List.of(
|
||||
new QRecord().withValue("id", 1).withValue("name", "People"),
|
||||
new QRecord().withValue("id", 2).withValue("name", "Planets")
|
||||
)));
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// assert we fail if no variant set in session when working with a table that needs them //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
assertThatThrownBy(() -> new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_MEMORY_VARIANT_DATA).withRecords(List.of(
|
||||
new QRecord().withValue("id", 1).withValue("name", "Tom")
|
||||
)))).hasMessageContaining("Could not find Backend Variant information");
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// set the variant in session, and assert we insert once //
|
||||
///////////////////////////////////////////////////////////
|
||||
QContext.getQSession().setBackendVariants(Map.of(TestUtils.TABLE_NAME_MEMORY_VARIANT_OPTIONS, 1));
|
||||
Integer peopleId1 = new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_MEMORY_VARIANT_DATA).withRecords(List.of(
|
||||
new QRecord().withValue("name", "Tom")
|
||||
))).getRecords().get(0).getValueInteger("id");
|
||||
assertEquals(1, peopleId1);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// set the other variant - make sure we insert, and get the same serial (e.g., they differ per variant) //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QContext.getQSession().setBackendVariants(Map.of(TestUtils.TABLE_NAME_MEMORY_VARIANT_OPTIONS, 2));
|
||||
Integer planetId1 = new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_MEMORY_VARIANT_DATA).withRecords(List.of(
|
||||
new QRecord().withValue("name", "Mercury"),
|
||||
new QRecord().withValue("name", "Venus"),
|
||||
new QRecord().withValue("name", "Earth")
|
||||
))).getRecords().get(0).getValueInteger("id");
|
||||
assertEquals(1, planetId1);
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// make sure counts return what we expect per-variant //
|
||||
////////////////////////////////////////////////////////
|
||||
QContext.getQSession().setBackendVariants(Map.of(TestUtils.TABLE_NAME_MEMORY_VARIANT_OPTIONS, 2));
|
||||
assertEquals(3, new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_MEMORY_VARIANT_DATA)).getCount());
|
||||
|
||||
QContext.getQSession().setBackendVariants(Map.of(TestUtils.TABLE_NAME_MEMORY_VARIANT_OPTIONS, 1));
|
||||
assertEquals(1, new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_MEMORY_VARIANT_DATA)).getCount());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -27,6 +27,7 @@ import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.PersonsByCreateDateBarChart;
|
||||
@ -114,9 +115,11 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAuto
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantsConfig;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.MockAuthenticationModule;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryModuleBackendVariantSetting;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.mock.MockBackendModule;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.basic.BasicETLProcess;
|
||||
@ -153,6 +156,10 @@ public class TestUtils
|
||||
public static final String TABLE_NAME_LINE_ITEM_EXTRINSIC = "orderLineExtrinsic";
|
||||
public static final String TABLE_NAME_ORDER_EXTRINSIC = "orderExtrinsic";
|
||||
|
||||
public static final String MEMORY_BACKEND_WITH_VARIANTS_NAME = "memoryWithVariants";
|
||||
public static final String TABLE_NAME_MEMORY_VARIANT_OPTIONS = "memoryVariantOptions";
|
||||
public static final String TABLE_NAME_MEMORY_VARIANT_DATA = "memoryVariantData";
|
||||
|
||||
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_INCREASE_BIRTHDATE = "increaseBirthdate";
|
||||
@ -255,6 +262,8 @@ public class TestUtils
|
||||
qInstance.addMessagingProvider(defineEmailMessagingProvider());
|
||||
qInstance.addMessagingProvider(defineSESMessagingProvider());
|
||||
|
||||
defineMemoryBackendVariantUseCases(qInstance);
|
||||
|
||||
defineWidgets(qInstance);
|
||||
defineApps(qInstance);
|
||||
|
||||
@ -265,6 +274,40 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static void defineMemoryBackendVariantUseCases(QInstance qInstance)
|
||||
{
|
||||
qInstance.addBackend(new QBackendMetaData()
|
||||
.withName(MEMORY_BACKEND_WITH_VARIANTS_NAME)
|
||||
.withBackendType(MemoryBackendModule.class)
|
||||
.withUsesVariants(true)
|
||||
.withBackendVariantsConfig(new BackendVariantsConfig()
|
||||
.withVariantTypeKey(TABLE_NAME_MEMORY_VARIANT_OPTIONS)
|
||||
.withOptionsTableName(TABLE_NAME_MEMORY_VARIANT_OPTIONS)
|
||||
.withBackendSettingSourceFieldNameMap(Map.of(MemoryModuleBackendVariantSetting.PRIMARY_KEY, "id"))
|
||||
));
|
||||
|
||||
qInstance.addTable(new QTableMetaData()
|
||||
.withName(TABLE_NAME_MEMORY_VARIANT_DATA)
|
||||
.withBackendName(MEMORY_BACKEND_WITH_VARIANTS_NAME)
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||
);
|
||||
|
||||
qInstance.addTable(new QTableMetaData()
|
||||
.withName(TABLE_NAME_MEMORY_VARIANT_OPTIONS)
|
||||
.withBackendName(MEMORY_BACKEND_NAME) // note, the version without variants!
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
Reference in New Issue
Block a user