Add Memory backend module

This commit is contained in:
2022-08-11 17:01:35 -05:00
parent 0029170978
commit d4186287ce
11 changed files with 853 additions and 4 deletions

View File

@ -72,6 +72,7 @@ public class QBackendModuleDispatcher
// todo - let modules somehow "export" their types here? // todo - let modules somehow "export" their types here?
// e.g., backend-core shouldn't need to "know" about the modules. // 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.mock.MockBackendModule",
"com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule",
"com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule", "com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule",
"com.kingsrook.qqq.backend.module.filesystem.local.FilesystemBackendModule", "com.kingsrook.qqq.backend.module.filesystem.local.FilesystemBackendModule",
"com.kingsrook.qqq.backend.module.filesystem.s3.S3BackendModule" "com.kingsrook.qqq.backend.module.filesystem.s3.S3BackendModule"

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<? extends QBackendMetaData> 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());
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, Map<Serializable, QRecord>> data;
private Map<String, Integer> 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<Serializable, QRecord> getTableData(QTableMetaData table)
{
if(!data.containsKey(table.getName()))
{
data.put(table.getName(), new HashMap<>());
}
return (data.get(table.getName()));
}
/*******************************************************************************
**
*******************************************************************************/
public List<QRecord> query(QueryInput input)
{
Map<Serializable, QRecord> tableData = getTableData(input.getTable());
List<QRecord> records = new ArrayList<>(tableData.values());
// todo - filtering
return (records);
}
/*******************************************************************************
**
*******************************************************************************/
public Integer count(CountInput input)
{
Map<Serializable, QRecord> tableData = getTableData(input.getTable());
List<QRecord> records = new ArrayList<>(tableData.values());
// todo - filtering
return (records.size());
}
/*******************************************************************************
**
*******************************************************************************/
public List<QRecord> insert(InsertInput input, boolean returnInsertedRecords)
{
if(input.getRecords() == null)
{
return (new ArrayList<>());
}
QTableMetaData table = input.getTable();
Map<Serializable, QRecord> tableData = getTableData(table);
Integer nextSerial = nextSerials.get(table.getName());
if(nextSerial == null)
{
nextSerial = 1;
while(tableData.containsKey(nextSerial))
{
nextSerial++;
}
}
List<QRecord> 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<QRecord> update(UpdateInput input, boolean returnUpdatedRecords)
{
if(input.getRecords() == null)
{
return (new ArrayList<>());
}
QTableMetaData table = input.getTable();
Map<Serializable, QRecord> tableData = getTableData(table);
List<QRecord> 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<String, Serializable> 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<Serializable, QRecord> tableData = getTableData(table);
int rowsDeleted = 0;
for(Serializable primaryKeyValue : input.getPrimaryKeys())
{
if(tableData.containsKey(primaryKeyValue))
{
tableData.remove(primaryKeyValue);
rowsDeleted++;
}
}
return (rowsDeleted);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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)));
}
}

View File

@ -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.AddAge;
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.GetAgeStatistics; 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.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.exceptions.QException;
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;
@ -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.model.session.QSession;
import com.kingsrook.qqq.backend.core.modules.authentication.MockAuthenticationModule; 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.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.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.basic.BasicETLProcess;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamed.StreamedETLProcess; 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 class TestUtils
{ {
public static final String DEFAULT_BACKEND_NAME = "default"; 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_GREETINGS = "greetingsApp";
public static final String APP_NAME_PEOPLE = "peopleApp"; public static final String APP_NAME_PEOPLE = "peopleApp";
public static final String APP_NAME_MISCELLANEOUS = "miscellaneous"; public static final String APP_NAME_MISCELLANEOUS = "miscellaneous";
public static final String TABLE_NAME_PERSON = "person"; 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 = "greet";
public static final String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive"; public static final String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive";
@ -89,10 +91,12 @@ public class TestUtils
QInstance qInstance = new QInstance(); QInstance qInstance = new QInstance();
qInstance.setAuthentication(defineAuthentication()); qInstance.setAuthentication(defineAuthentication());
qInstance.addBackend(defineBackend()); qInstance.addBackend(defineBackend());
qInstance.addBackend(defineMemoryBackend());
qInstance.addTable(defineTablePerson()); qInstance.addTable(defineTablePerson());
qInstance.addTable(definePersonFileTable()); qInstance.addTable(definePersonFileTable());
qInstance.addTable(defineTableIdAndNameOnly()); qInstance.addTable(defineTableIdAndNameOnly());
qInstance.addTable(defineTableShape());
qInstance.addPossibleValueSource(defineStatesPossibleValueSource()); qInstance.addPossibleValueSource(defineStatesPossibleValueSource());
@ -104,8 +108,6 @@ public class TestUtils
defineApps(qInstance); defineApps(qInstance);
System.out.println(new QInstanceAdapter().qInstanceToJson(qInstance));
return (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. ** 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) ** Define a 2nd version of the 'person' table for this test (pretend it's backed by a file)
*******************************************************************************/ *******************************************************************************/

View File

@ -91,7 +91,7 @@ public class FilesystemSyncStep implements BackendStep
String sourceFileName = sourceEntry.getKey(); String sourceFileName = sourceEntry.getKey();
if(!archiveFiles.contains(sourceFileName)) 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()); InputStream inputStream = sourceActionBase.readFile(sourceEntry.getValue());
byte[] bytes = inputStream.readAllBytes(); byte[] bytes = inputStream.readAllBytes();