From d4186287ceacd24052949ce7ec7007b6e605f5d9 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 11 Aug 2022 17:01:35 -0500 Subject: [PATCH] Add Memory backend module --- .../backend/QBackendModuleDispatcher.java | 1 + .../memory/MemoryBackendModule.java | 116 +++++++++ .../memory/MemoryCountAction.java | 54 ++++ .../memory/MemoryDeleteAction.java | 55 ++++ .../memory/MemoryInsertAction.java | 55 ++++ .../memory/MemoryQueryAction.java | 55 ++++ .../memory/MemoryRecordStore.java | 238 ++++++++++++++++++ .../memory/MemoryUpdateAction.java | 55 ++++ .../memory/MemoryBackendModuleTest.java | 185 ++++++++++++++ .../qqq/backend/core/utils/TestUtils.java | 41 ++- .../filesystem/sync/FilesystemSyncStep.java | 2 +- 11 files changed, 853 insertions(+), 4 deletions(-) create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModule.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryCountAction.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryDeleteAction.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryInsertAction.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryQueryAction.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryUpdateAction.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModuleTest.java diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/QBackendModuleDispatcher.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/QBackendModuleDispatcher.java index c57afd5b..19161379 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/QBackendModuleDispatcher.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/QBackendModuleDispatcher.java @@ -72,6 +72,7 @@ public class QBackendModuleDispatcher // todo - let modules somehow "export" their types here? // e.g., backend-core shouldn't need to "know" about the modules. "com.kingsrook.qqq.backend.core.modules.backend.implementations.mock.MockBackendModule", + "com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule", "com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule", "com.kingsrook.qqq.backend.module.filesystem.local.FilesystemBackendModule", "com.kingsrook.qqq.backend.module.filesystem.s3.S3BackendModule" diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModule.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModule.java new file mode 100644 index 00000000..84852cb9 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModule.java @@ -0,0 +1,116 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory; + + +import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface; +import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface; +import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface; +import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface; +import com.kingsrook.qqq.backend.core.actions.interfaces.UpdateInterface; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface; + + +/******************************************************************************* + ** A simple (probably only valid for testing?) implementation of the QModuleInterface, + ** that just stores its records in-memory. + ** + *******************************************************************************/ +public class MemoryBackendModule implements QBackendModuleInterface +{ + /******************************************************************************* + ** Method where a backend module must be able to provide its type (name). + *******************************************************************************/ + @Override + public String getBackendType() + { + return ("memory"); + } + + + + /******************************************************************************* + ** Method to identify the class used for backend meta data for this module. + *******************************************************************************/ + @Override + public Class getBackendMetaDataClass() + { + return (QBackendMetaData.class); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public CountInterface getCountInterface() + { + return new MemoryCountAction(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public QueryInterface getQueryInterface() + { + return new MemoryQueryAction(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public InsertInterface getInsertInterface() + { + return (new MemoryInsertAction()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public UpdateInterface getUpdateInterface() + { + return (new MemoryUpdateAction()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public DeleteInterface getDeleteInterface() + { + return (new MemoryDeleteAction()); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryCountAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryCountAction.java new file mode 100644 index 00000000..4f5e0d67 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryCountAction.java @@ -0,0 +1,54 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory; + + +import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput; + + +/******************************************************************************* + ** In-memory version of count action. + ** + *******************************************************************************/ +public class MemoryCountAction implements CountInterface +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public CountOutput execute(CountInput countInput) throws QException + { + try + { + CountOutput countOutput = new CountOutput(); + countOutput.setCount(MemoryRecordStore.getInstance().count(countInput)); + return (countOutput); + } + catch(Exception e) + { + throw new QException("Error executing count", e); + } + } +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryDeleteAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryDeleteAction.java new file mode 100644 index 00000000..50c5e239 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryDeleteAction.java @@ -0,0 +1,55 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory; + + +import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput; + + +/******************************************************************************* + ** In-memory version of delete action. + ** + *******************************************************************************/ +public class MemoryDeleteAction implements DeleteInterface +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public DeleteOutput execute(DeleteInput deleteInput) throws QException + { + try + { + DeleteOutput deleteOutput = new DeleteOutput(); + deleteOutput.setDeletedRecordCount(MemoryRecordStore.getInstance().delete(deleteInput)); + return (deleteOutput); + } + catch(Exception e) + { + throw new QException("Error executing delete: " + e.getMessage(), e); + } + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryInsertAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryInsertAction.java new file mode 100644 index 00000000..01839359 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryInsertAction.java @@ -0,0 +1,55 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory; + + +import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput; + + +/******************************************************************************* + ** In-memory version of insert action. + ** + *******************************************************************************/ +public class MemoryInsertAction implements InsertInterface +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public InsertOutput execute(InsertInput insertInput) throws QException + { + try + { + InsertOutput insertOutput = new InsertOutput(); + insertOutput.setRecords(MemoryRecordStore.getInstance().insert(insertInput, true)); + return (insertOutput); + } + catch(Exception e) + { + throw new QException("Error executing insert: " + e.getMessage(), e); + } + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryQueryAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryQueryAction.java new file mode 100644 index 00000000..cd0e8bf7 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryQueryAction.java @@ -0,0 +1,55 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory; + + +import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; + + +/******************************************************************************* + ** In-memory version of query action. + ** + *******************************************************************************/ +public class MemoryQueryAction implements QueryInterface +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public QueryOutput execute(QueryInput queryInput) throws QException + { + try + { + QueryOutput queryOutput = new QueryOutput(queryInput); + queryOutput.addRecords(MemoryRecordStore.getInstance().query(queryInput)); + return (queryOutput); + } + catch(Exception e) + { + throw new QException("Error executing query", e); + } + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java new file mode 100644 index 00000000..8fc7a02b --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java @@ -0,0 +1,238 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory; + + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; +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.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** Storage provider for the MemoryBackendModule + *******************************************************************************/ +public class MemoryRecordStore +{ + private static MemoryRecordStore instance; + + private Map> data; + private Map nextSerials; + + + + /******************************************************************************* + ** private singleton constructor + *******************************************************************************/ + private MemoryRecordStore() + { + data = new HashMap<>(); + nextSerials = new HashMap<>(); + } + + + + /******************************************************************************* + ** Forget all data in the memory store... + *******************************************************************************/ + public void reset() + { + data.clear(); + nextSerials.clear(); + } + + + + /******************************************************************************* + ** singleton accessor + *******************************************************************************/ + public static MemoryRecordStore getInstance() + { + if(instance == null) + { + instance = new MemoryRecordStore(); + } + return (instance); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private Map getTableData(QTableMetaData table) + { + if(!data.containsKey(table.getName())) + { + data.put(table.getName(), new HashMap<>()); + } + return (data.get(table.getName())); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public List query(QueryInput input) + { + Map tableData = getTableData(input.getTable()); + List records = new ArrayList<>(tableData.values()); + // todo - filtering + return (records); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Integer count(CountInput input) + { + Map tableData = getTableData(input.getTable()); + List records = new ArrayList<>(tableData.values()); + // todo - filtering + return (records.size()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public List insert(InsertInput input, boolean returnInsertedRecords) + { + if(input.getRecords() == null) + { + return (new ArrayList<>()); + } + + QTableMetaData table = input.getTable(); + Map tableData = getTableData(table); + Integer nextSerial = nextSerials.get(table.getName()); + if(nextSerial == null) + { + nextSerial = 1; + while(tableData.containsKey(nextSerial)) + { + nextSerial++; + } + } + + List outputRecords = new ArrayList<>(); + QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField()); + for(QRecord record : input.getRecords()) + { + if(record.getValue(primaryKeyField.getName()) == null && primaryKeyField.getType().equals(QFieldType.INTEGER)) + { + record.setValue(primaryKeyField.getName(), nextSerial++); + } + + tableData.put(record.getValue(primaryKeyField.getName()), record); + if(returnInsertedRecords) + { + outputRecords.add(record); + } + } + + return (outputRecords); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public List update(UpdateInput input, boolean returnUpdatedRecords) + { + if(input.getRecords() == null) + { + return (new ArrayList<>()); + } + + QTableMetaData table = input.getTable(); + Map tableData = getTableData(table); + + List outputRecords = new ArrayList<>(); + QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField()); + for(QRecord record : input.getRecords()) + { + Serializable primaryKeyValue = record.getValue(primaryKeyField.getName()); + if(tableData.containsKey(primaryKeyValue)) + { + QRecord recordToUpdate = tableData.get(primaryKeyValue); + for(Map.Entry valueEntry : record.getValues().entrySet()) + { + recordToUpdate.setValue(valueEntry.getKey(), valueEntry.getValue()); + } + + if(returnUpdatedRecords) + { + outputRecords.add(record); + } + } + else + { + outputRecords.add(record); + } + } + + return (outputRecords); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public int delete(DeleteInput input) + { + if(input.getPrimaryKeys() == null) + { + return (0); + } + + QTableMetaData table = input.getTable(); + Map tableData = getTableData(table); + int rowsDeleted = 0; + for(Serializable primaryKeyValue : input.getPrimaryKeys()) + { + if(tableData.containsKey(primaryKeyValue)) + { + tableData.remove(primaryKeyValue); + rowsDeleted++; + } + } + + return (rowsDeleted); + } +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryUpdateAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryUpdateAction.java new file mode 100644 index 00000000..97793ce6 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryUpdateAction.java @@ -0,0 +1,55 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory; + + +import com.kingsrook.qqq.backend.core.actions.interfaces.UpdateInterface; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput; + + +/******************************************************************************* + ** In-memory version of update action. + ** + *******************************************************************************/ +public class MemoryUpdateAction implements UpdateInterface +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public UpdateOutput execute(UpdateInput updateInput) throws QException + { + try + { + UpdateOutput updateOutput = new UpdateOutput(); + updateOutput.setRecords(MemoryRecordStore.getInstance().update(updateInput, true)); + return (updateOutput); + } + catch(Exception e) + { + throw new QException("Error executing update: " + e.getMessage(), e); + } + } + +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModuleTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModuleTest.java new file mode 100644 index 00000000..f07e1b9a --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModuleTest.java @@ -0,0 +1,185 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.actions.tables.CountAction; +import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; +import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; +import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; +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.InsertOutput; +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.update.UpdateInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput; +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.tables.QTableMetaData; +import com.kingsrook.qqq.backend.core.model.session.QSession; +import com.kingsrook.qqq.backend.core.utils.TestUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/******************************************************************************* + ** Unit test for MemoryBackendModule + *******************************************************************************/ +class MemoryBackendModuleTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @AfterEach + void afterEach() + { + MemoryRecordStore.getInstance().reset(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testFullCRUD() throws QException + { + QInstance qInstance = TestUtils.defineInstance(); + QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_SHAPE); + QSession session = new QSession(); + + ///////////////////////// + // do an initial count // + ///////////////////////// + CountInput countInput = new CountInput(qInstance); + countInput.setSession(session); + countInput.setTableName(table.getName()); + assertEquals(0, new CountAction().execute(countInput).getCount()); + + ////////////////// + // do an insert // + ////////////////// + InsertInput insertInput = new InsertInput(qInstance); + insertInput.setSession(session); + insertInput.setTableName(table.getName()); + insertInput.setRecords(List.of( + new QRecord() + .withTableName(table.getName()) + .withValue("name", "My Triangle") + .withValue("type", "triangle") + .withValue("noOfSides", 3) + .withValue("isPolygon", true), + new QRecord() + .withTableName(table.getName()) + .withValue("name", "Your Square") + .withValue("type", "square") + .withValue("noOfSides", 4) + .withValue("isPolygon", true), + new QRecord() + .withTableName(table.getName()) + .withValue("name", "Some Circle") + .withValue("type", "circle") + .withValue("noOfSides", null) + .withValue("isPolygon", false) + )); + InsertOutput insertOutput = new InsertAction().execute(insertInput); + assertEquals(insertOutput.getRecords().size(), 3); + assertTrue(insertOutput.getRecords().stream().allMatch(r -> r.getValue("id") != null)); + assertTrue(insertOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1))); + assertTrue(insertOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2))); + assertTrue(insertOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3))); + + //////////////// + // do a query // + //////////////// + QueryInput queryInput = new QueryInput(qInstance); + queryInput.setSession(session); + queryInput.setTableName(table.getName()); + QueryOutput queryOutput = new QueryAction().execute(queryInput); + assertEquals(queryOutput.getRecords().size(), 3); + assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValue("id") != null)); + assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1))); + assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2))); + assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3))); + assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("name").equals("My Triangle"))); + assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("name").equals("Your Square"))); + assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("name").equals("Some Circle"))); + + assertEquals(3, new CountAction().execute(countInput).getCount()); + + ////////////////// + // do an update // + ////////////////// + UpdateInput updateInput = new UpdateInput(qInstance); + updateInput.setSession(session); + updateInput.setTableName(table.getName()); + updateInput.setRecords(List.of( + new QRecord() + .withTableName(table.getName()) + .withValue("id", 1) + .withValue("name", "Not My Triangle any more"), + new QRecord() + .withTableName(table.getName()) + .withValue("id", 3) + .withValue("type", "ellipse") + )); + UpdateOutput updateOutput = new UpdateAction().execute(updateInput); + assertEquals(updateOutput.getRecords().size(), 2); + + queryOutput = new QueryAction().execute(queryInput); + assertEquals(queryOutput.getRecords().size(), 3); + assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("name").equals("My Triangle"))); + assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("name").equals("Not My Triangle any more"))); + assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("type").equals("ellipse"))); + assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("type").equals("circle"))); + + assertEquals(3, new CountAction().execute(countInput).getCount()); + + ///////////////// + // do a delete // + ///////////////// + DeleteInput deleteInput = new DeleteInput(qInstance); + deleteInput.setSession(session); + deleteInput.setTableName(table.getName()); + deleteInput.setPrimaryKeys(List.of(1, 2)); + DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput); + assertEquals(deleteOutput.getDeletedRecordCount(), 2); + + assertEquals(1, new CountAction().execute(countInput).getCount()); + + queryOutput = new QueryAction().execute(queryInput); + assertEquals(queryOutput.getRecords().size(), 1); + assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueInteger("id").equals(1))); + assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueInteger("id").equals(2))); + assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3))); + } + +} \ No newline at end of file diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java index a0711f87..aa537948 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java @@ -26,7 +26,6 @@ import java.util.List; import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.AddAge; import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.GetAgeStatistics; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; -import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; @@ -52,6 +51,7 @@ 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.modules.authentication.MockAuthenticationModule; import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData; +import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule; import com.kingsrook.qqq.backend.core.modules.backend.implementations.mock.MockBackendModule; import com.kingsrook.qqq.backend.core.processes.implementations.etl.basic.BasicETLProcess; import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamed.StreamedETLProcess; @@ -65,12 +65,14 @@ import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackend public class TestUtils { public static final String DEFAULT_BACKEND_NAME = "default"; + public static final String MEMORY_BACKEND_NAME = "memory"; public static final String APP_NAME_GREETINGS = "greetingsApp"; public static final String APP_NAME_PEOPLE = "peopleApp"; public static final String APP_NAME_MISCELLANEOUS = "miscellaneous"; public static final String TABLE_NAME_PERSON = "person"; + public static final String TABLE_NAME_SHAPE = "shape"; public static final String PROCESS_NAME_GREET_PEOPLE = "greet"; public static final String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive"; @@ -89,10 +91,12 @@ public class TestUtils QInstance qInstance = new QInstance(); qInstance.setAuthentication(defineAuthentication()); qInstance.addBackend(defineBackend()); + qInstance.addBackend(defineMemoryBackend()); qInstance.addTable(defineTablePerson()); qInstance.addTable(definePersonFileTable()); qInstance.addTable(defineTableIdAndNameOnly()); + qInstance.addTable(defineTableShape()); qInstance.addPossibleValueSource(defineStatesPossibleValueSource()); @@ -104,8 +108,6 @@ public class TestUtils defineApps(qInstance); - System.out.println(new QInstanceAdapter().qInstanceToJson(qInstance)); - return (qInstance); } @@ -174,6 +176,18 @@ public class TestUtils + /******************************************************************************* + ** Define the in-memory backend used in standard tests + *******************************************************************************/ + public static QBackendMetaData defineMemoryBackend() + { + return new QBackendMetaData() + .withName(MEMORY_BACKEND_NAME) + .withBackendType(MemoryBackendModule.class); + } + + + /******************************************************************************* ** Define the 'person' table used in standard tests. *******************************************************************************/ @@ -196,6 +210,27 @@ public class TestUtils + /******************************************************************************* + ** Define the 'shape' table used in standard tests. + *******************************************************************************/ + public static QTableMetaData defineTableShape() + { + return new QTableMetaData() + .withName(TABLE_NAME_SHAPE) + .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("name", QFieldType.STRING)) + .withField(new QFieldMetaData("type", QFieldType.STRING)) // todo PVS + .withField(new QFieldMetaData("noOfSides", QFieldType.INTEGER)) + .withField(new QFieldMetaData("isPolygon", QFieldType.BOOLEAN)) // mmm, should be derived from type, no? + ; + } + + + /******************************************************************************* ** Define a 2nd version of the 'person' table for this test (pretend it's backed by a file) *******************************************************************************/ diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/sync/FilesystemSyncStep.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/sync/FilesystemSyncStep.java index 1ea36990..8464bb22 100644 --- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/sync/FilesystemSyncStep.java +++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/sync/FilesystemSyncStep.java @@ -91,7 +91,7 @@ public class FilesystemSyncStep implements BackendStep String sourceFileName = sourceEntry.getKey(); if(!archiveFiles.contains(sourceFileName)) { - LOG.info("Syncing file [" + sourceFileName + "] to [" + archiveTable + "] and [" + processingTable + "]"); + LOG.info("Syncing file [" + sourceFileName + "] to [" + archiveTable.getName() + "] and [" + processingTable.getName() + "]"); InputStream inputStream = sourceActionBase.readFile(sourceEntry.getValue()); byte[] bytes = inputStream.readAllBytes();