mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Bulk update & delete; errors if more than jsut the expected json
This commit is contained in:
@ -257,6 +257,14 @@ public class MemoryRecordStore
|
|||||||
for(QRecord record : input.getRecords())
|
for(QRecord record : input.getRecords())
|
||||||
{
|
{
|
||||||
Serializable primaryKeyValue = ValueUtils.getValueAsFieldType(primaryKeyField.getType(), record.getValue(primaryKeyField.getName()));
|
Serializable primaryKeyValue = ValueUtils.getValueAsFieldType(primaryKeyField.getType(), record.getValue(primaryKeyField.getName()));
|
||||||
|
|
||||||
|
if(primaryKeyValue == null)
|
||||||
|
{
|
||||||
|
record.addError("Missing value in primary key field");
|
||||||
|
outputRecords.add(record);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if(tableData.containsKey(primaryKeyValue))
|
if(tableData.containsKey(primaryKeyValue))
|
||||||
{
|
{
|
||||||
QRecord recordToUpdate = tableData.get(primaryKeyValue);
|
QRecord recordToUpdate = tableData.get(primaryKeyValue);
|
||||||
|
@ -87,6 +87,7 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
|
|||||||
// record. So, we will first "hash" up the records by their list of fields being updated. //
|
// record. So, we will first "hash" up the records by their list of fields being updated. //
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
ListingHash<List<String>, QRecord> recordsByFieldBeingUpdated = new ListingHash<>();
|
ListingHash<List<String>, QRecord> recordsByFieldBeingUpdated = new ListingHash<>();
|
||||||
|
boolean haveAnyWithoutErorrs = false;
|
||||||
for(QRecord record : updateInput.getRecords())
|
for(QRecord record : updateInput.getRecords())
|
||||||
{
|
{
|
||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
@ -103,6 +104,19 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
|
|||||||
.toList();
|
.toList();
|
||||||
recordsByFieldBeingUpdated.add(updatableFields, record);
|
recordsByFieldBeingUpdated.add(updatableFields, record);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// to update a record, we must have its primary key value - so - check - if it's missing, mark it as an error //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(record.getValue(table.getPrimaryKeyField()) == null)
|
||||||
|
{
|
||||||
|
record.addError("Missing value in primary key field");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(CollectionUtils.nullSafeIsEmpty(record.getErrors()))
|
||||||
|
{
|
||||||
|
haveAnyWithoutErorrs = true;
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// go ahead and put the record into the output list at this point in time, //
|
// go ahead and put the record into the output list at this point in time, //
|
||||||
// so that the output list's order matches the input list order //
|
// so that the output list's order matches the input list order //
|
||||||
@ -113,6 +127,12 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
|
|||||||
outputRecords.add(outputRecord);
|
outputRecords.add(outputRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!haveAnyWithoutErorrs)
|
||||||
|
{
|
||||||
|
LOG.info("Exiting early - all records have some error.");
|
||||||
|
return (rs);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Connection connection;
|
Connection connection;
|
||||||
@ -192,6 +212,11 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
|
|||||||
List<List<Serializable>> values = new ArrayList<>();
|
List<List<Serializable>> values = new ArrayList<>();
|
||||||
for(QRecord record : recordList)
|
for(QRecord record : recordList)
|
||||||
{
|
{
|
||||||
|
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
List<Serializable> rowValues = new ArrayList<>();
|
List<Serializable> rowValues = new ArrayList<>();
|
||||||
values.add(rowValues);
|
values.add(rowValues);
|
||||||
|
|
||||||
@ -204,6 +229,14 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
|
|||||||
rowValues.add(record.getValue(table.getPrimaryKeyField()));
|
rowValues.add(record.getValue(table.getPrimaryKeyField()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(values.isEmpty())
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if all records had errors, so we didn't push any values, then return early //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Long mark = System.currentTimeMillis();
|
Long mark = System.currentTimeMillis();
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -241,6 +274,15 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
|
|||||||
{
|
{
|
||||||
for(List<QRecord> page : CollectionUtils.getPages(recordList, QueryManager.PAGE_SIZE))
|
for(List<QRecord> page : CollectionUtils.getPages(recordList, QueryManager.PAGE_SIZE))
|
||||||
{
|
{
|
||||||
|
//////////////////////////////
|
||||||
|
// skip records with errors //
|
||||||
|
//////////////////////////////
|
||||||
|
page = page.stream().filter(r -> CollectionUtils.nullSafeIsEmpty(r.getErrors())).collect(Collectors.toList());
|
||||||
|
if(page.isEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
String sql = writeUpdateSQLPrefix(table, fieldsBeingUpdated) + " IN (" + StringUtils.join(",", Collections.nCopies(page.size(), "?")) + ")";
|
String sql = writeUpdateSQLPrefix(table, fieldsBeingUpdated) + " IN (" + StringUtils.join(",", Collections.nCopies(page.size(), "?")) + ")";
|
||||||
|
|
||||||
// todo sql customization? - let each table have custom sql and/or param list
|
// todo sql customization? - let each table have custom sql and/or param list
|
||||||
@ -293,6 +335,15 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
|
|||||||
for(int i = 1; i < recordList.size(); i++)
|
for(int i = 1; i < recordList.size(); i++)
|
||||||
{
|
{
|
||||||
QRecord record = recordList.get(i);
|
QRecord record = recordList.get(i);
|
||||||
|
|
||||||
|
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
// skip records w/ errors (that we won't be updating //
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for(String fieldName : fieldsBeingUpdated)
|
for(String fieldName : fieldsBeingUpdated)
|
||||||
{
|
{
|
||||||
if(!Objects.equals(record0.getValue(fieldName), record.getValue(fieldName)))
|
if(!Objects.equals(record0.getValue(fieldName), record.getValue(fieldName)))
|
||||||
|
@ -26,7 +26,10 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||||
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.get.GetInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
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.actions.tables.update.UpdateOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
@ -36,6 +39,7 @@ import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
@ -98,7 +102,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
|
|||||||
public void testUpdateOne() throws Exception
|
public void testUpdateOne() throws Exception
|
||||||
{
|
{
|
||||||
UpdateInput updateInput = initUpdateRequest();
|
UpdateInput updateInput = initUpdateRequest();
|
||||||
QRecord record = new QRecord().withTableName("person")
|
QRecord record = new QRecord()
|
||||||
.withValue("id", 2)
|
.withValue("id", 2)
|
||||||
.withValue("firstName", "James")
|
.withValue("firstName", "James")
|
||||||
.withValue("lastName", "Kirk")
|
.withValue("lastName", "Kirk")
|
||||||
@ -141,18 +145,18 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
|
|||||||
public void testUpdateManyWithDifferentColumnsAndValues() throws Exception
|
public void testUpdateManyWithDifferentColumnsAndValues() throws Exception
|
||||||
{
|
{
|
||||||
UpdateInput updateInput = initUpdateRequest();
|
UpdateInput updateInput = initUpdateRequest();
|
||||||
QRecord record1 = new QRecord().withTableName("person")
|
QRecord record1 = new QRecord()
|
||||||
.withValue("id", 1)
|
.withValue("id", 1)
|
||||||
.withValue("firstName", "Darren")
|
.withValue("firstName", "Darren")
|
||||||
.withValue("lastName", "From Bewitched")
|
.withValue("lastName", "From Bewitched")
|
||||||
.withValue("birthDate", "1900-01-01");
|
.withValue("birthDate", "1900-01-01");
|
||||||
|
|
||||||
QRecord record2 = new QRecord().withTableName("person")
|
QRecord record2 = new QRecord()
|
||||||
.withValue("id", 3)
|
.withValue("id", 3)
|
||||||
.withValue("firstName", "Wilt")
|
.withValue("firstName", "Wilt")
|
||||||
.withValue("birthDate", null);
|
.withValue("birthDate", null);
|
||||||
|
|
||||||
QRecord record3 = new QRecord().withTableName("person")
|
QRecord record3 = new QRecord()
|
||||||
.withValue("id", 5)
|
.withValue("id", 5)
|
||||||
.withValue("firstName", "Richard")
|
.withValue("firstName", "Richard")
|
||||||
.withValue("birthDate", null);
|
.withValue("birthDate", null);
|
||||||
@ -216,13 +220,13 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
|
|||||||
public void testUpdateManyWithSameColumnsDifferentValues() throws Exception
|
public void testUpdateManyWithSameColumnsDifferentValues() throws Exception
|
||||||
{
|
{
|
||||||
UpdateInput updateInput = initUpdateRequest();
|
UpdateInput updateInput = initUpdateRequest();
|
||||||
QRecord record1 = new QRecord().withTableName("person")
|
QRecord record1 = new QRecord()
|
||||||
.withValue("id", 1)
|
.withValue("id", 1)
|
||||||
.withValue("firstName", "Darren")
|
.withValue("firstName", "Darren")
|
||||||
.withValue("lastName", "From Bewitched")
|
.withValue("lastName", "From Bewitched")
|
||||||
.withValue("birthDate", "1900-01-01");
|
.withValue("birthDate", "1900-01-01");
|
||||||
|
|
||||||
QRecord record2 = new QRecord().withTableName("person")
|
QRecord record2 = new QRecord()
|
||||||
.withValue("id", 3)
|
.withValue("id", 3)
|
||||||
.withValue("firstName", "Wilt")
|
.withValue("firstName", "Wilt")
|
||||||
.withValue("lastName", "Tim's Uncle")
|
.withValue("lastName", "Tim's Uncle")
|
||||||
@ -276,7 +280,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
|
|||||||
List<QRecord> records = new ArrayList<>();
|
List<QRecord> records = new ArrayList<>();
|
||||||
for(int i = 1; i <= 5; i++)
|
for(int i = 1; i <= 5; i++)
|
||||||
{
|
{
|
||||||
records.add(new QRecord().withTableName("person")
|
records.add(new QRecord()
|
||||||
.withValue("id", i)
|
.withValue("id", i)
|
||||||
.withValue("birthDate", "1999-09-09"));
|
.withValue("birthDate", "1999-09-09"));
|
||||||
}
|
}
|
||||||
@ -312,7 +316,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
|
|||||||
|
|
||||||
UpdateInput updateInput = initUpdateRequest();
|
UpdateInput updateInput = initUpdateRequest();
|
||||||
List<QRecord> records = new ArrayList<>();
|
List<QRecord> records = new ArrayList<>();
|
||||||
records.add(new QRecord().withTableName("person")
|
records.add(new QRecord()
|
||||||
.withValue("id", 1)
|
.withValue("id", 1)
|
||||||
.withValue("firstName", "Johnny Updated"));
|
.withValue("firstName", "Johnny Updated"));
|
||||||
updateInput.setRecords(records);
|
updateInput.setRecords(records);
|
||||||
@ -336,7 +340,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
|
|||||||
{
|
{
|
||||||
UpdateInput updateInput = initUpdateRequest();
|
UpdateInput updateInput = initUpdateRequest();
|
||||||
List<QRecord> records = new ArrayList<>();
|
List<QRecord> records = new ArrayList<>();
|
||||||
records.add(new QRecord().withTableName("person")
|
records.add(new QRecord()
|
||||||
.withValue("id", 1)
|
.withValue("id", 1)
|
||||||
.withValue("createDate", "2022-10-03T10:29:35Z")
|
.withValue("createDate", "2022-10-03T10:29:35Z")
|
||||||
.withValue("firstName", "Johnny Updated"));
|
.withValue("firstName", "Johnny Updated"));
|
||||||
@ -346,6 +350,63 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Make sure that records without a primary key come back with error.
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testWithoutPrimaryKeyErrors() throws Exception
|
||||||
|
{
|
||||||
|
{
|
||||||
|
UpdateInput updateInput = initUpdateRequest();
|
||||||
|
List<QRecord> records = new ArrayList<>();
|
||||||
|
records.add(new QRecord()
|
||||||
|
.withValue("firstName", "Johnny Updated"));
|
||||||
|
updateInput.setRecords(records);
|
||||||
|
UpdateOutput updateOutput = new RDBMSUpdateAction().execute(updateInput);
|
||||||
|
assertFalse(updateOutput.getRecords().get(0).getErrors().isEmpty());
|
||||||
|
assertEquals("Missing value in primary key field", updateOutput.getRecords().get(0).getErrors().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
UpdateInput updateInput = initUpdateRequest();
|
||||||
|
List<QRecord> records = new ArrayList<>();
|
||||||
|
records.add(new QRecord()
|
||||||
|
.withValue("id", null)
|
||||||
|
.withValue("firstName", "Johnny Updated"));
|
||||||
|
updateInput.setRecords(records);
|
||||||
|
UpdateOutput updateOutput = new RDBMSUpdateAction().execute(updateInput);
|
||||||
|
assertFalse(updateOutput.getRecords().get(0).getErrors().isEmpty());
|
||||||
|
assertEquals("Missing value in primary key field", updateOutput.getRecords().get(0).getErrors().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
UpdateInput updateInput = initUpdateRequest();
|
||||||
|
List<QRecord> records = new ArrayList<>();
|
||||||
|
records.add(new QRecord()
|
||||||
|
.withValue("id", null)
|
||||||
|
.withValue("firstName", "Johnny Not Updated"));
|
||||||
|
records.add(new QRecord()
|
||||||
|
.withValue("id", 2)
|
||||||
|
.withValue("firstName", "Johnny Updated"));
|
||||||
|
updateInput.setRecords(records);
|
||||||
|
UpdateOutput updateOutput = new RDBMSUpdateAction().execute(updateInput);
|
||||||
|
|
||||||
|
assertFalse(updateOutput.getRecords().get(0).getErrors().isEmpty());
|
||||||
|
assertEquals("Missing value in primary key field", updateOutput.getRecords().get(0).getErrors().get(0));
|
||||||
|
|
||||||
|
assertTrue(updateOutput.getRecords().get(1).getErrors().isEmpty());
|
||||||
|
|
||||||
|
GetInput getInput = new GetInput();
|
||||||
|
getInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
getInput.setPrimaryKey(2);
|
||||||
|
GetOutput getOutput = new GetAction().execute(getInput);
|
||||||
|
assertEquals("Johnny Updated", getOutput.getRecord().getValueString("firstName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -369,7 +430,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
|
|||||||
private UpdateInput initUpdateRequest()
|
private UpdateInput initUpdateRequest()
|
||||||
{
|
{
|
||||||
UpdateInput updateInput = new UpdateInput();
|
UpdateInput updateInput = new UpdateInput();
|
||||||
updateInput.setTableName(TestUtils.defineTablePerson().getName());
|
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
return updateInput;
|
return updateInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,6 +91,7 @@ import org.apache.commons.lang.BooleanUtils;
|
|||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONTokener;
|
||||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
import static com.kingsrook.qqq.backend.javalin.QJavalinImplementation.SLOW_LOG_THRESHOLD_MS;
|
import static com.kingsrook.qqq.backend.javalin.QJavalinImplementation.SLOW_LOG_THRESHOLD_MS;
|
||||||
|
|
||||||
@ -137,13 +138,16 @@ public class QJavalinApiHandler
|
|||||||
ApiBuilder.get("/query", QJavalinApiHandler::doQuery);
|
ApiBuilder.get("/query", QJavalinApiHandler::doQuery);
|
||||||
// ApiBuilder.post("/query", QJavalinApiHandler::doQuery);
|
// ApiBuilder.post("/query", QJavalinApiHandler::doQuery);
|
||||||
|
|
||||||
|
ApiBuilder.post("/bulk", QJavalinApiHandler::bulkInsert);
|
||||||
|
ApiBuilder.patch("/bulk", QJavalinApiHandler::bulkUpdate);
|
||||||
|
ApiBuilder.delete("/bulk", QJavalinApiHandler::bulkDelete);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
// remember to keep the wildcard paths after the specific paths //
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
ApiBuilder.get("/{primaryKey}", QJavalinApiHandler::doGet);
|
ApiBuilder.get("/{primaryKey}", QJavalinApiHandler::doGet);
|
||||||
ApiBuilder.patch("/{primaryKey}", QJavalinApiHandler::doUpdate);
|
ApiBuilder.patch("/{primaryKey}", QJavalinApiHandler::doUpdate);
|
||||||
ApiBuilder.delete("/{primaryKey}", QJavalinApiHandler::doDelete);
|
ApiBuilder.delete("/{primaryKey}", QJavalinApiHandler::doDelete);
|
||||||
|
|
||||||
ApiBuilder.post("/bulk", QJavalinApiHandler::bulkInsert);
|
|
||||||
// patch("/bulk", QJavalinApiHandler::bulkUpdate);
|
|
||||||
// delete("/bulk", QJavalinApiHandler::bulkDelete);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -722,8 +726,15 @@ public class QJavalinApiHandler
|
|||||||
throw (new QBadRequestException("Missing required POST body"));
|
throw (new QBadRequestException("Missing required POST body"));
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject jsonObject = new JSONObject(context.body());
|
JSONTokener jsonTokener = new JSONTokener(context.body().trim());
|
||||||
|
JSONObject jsonObject = new JSONObject(jsonTokener);
|
||||||
|
|
||||||
insertInput.setRecords(List.of(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version)));
|
insertInput.setRecords(List.of(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version)));
|
||||||
|
|
||||||
|
if(jsonTokener.more())
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("Body contained more than a single JSON object."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(QBadRequestException qbre)
|
catch(QBadRequestException qbre)
|
||||||
{
|
{
|
||||||
@ -787,13 +798,21 @@ public class QJavalinApiHandler
|
|||||||
|
|
||||||
ArrayList<QRecord> recordList = new ArrayList<>();
|
ArrayList<QRecord> recordList = new ArrayList<>();
|
||||||
insertInput.setRecords(recordList);
|
insertInput.setRecords(recordList);
|
||||||
JSONArray jsonArray = new JSONArray(context.body());
|
|
||||||
|
JSONTokener jsonTokener = new JSONTokener(context.body().trim());
|
||||||
|
JSONArray jsonArray = new JSONArray(jsonTokener);
|
||||||
|
|
||||||
for(int i = 0; i < jsonArray.length(); i++)
|
for(int i = 0; i < jsonArray.length(); i++)
|
||||||
{
|
{
|
||||||
JSONObject jsonObject = jsonArray.getJSONObject(i);
|
JSONObject jsonObject = jsonArray.getJSONObject(i);
|
||||||
recordList.add(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version));
|
recordList.add(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(jsonTokener.more())
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("Body contained more than a single JSON array."));
|
||||||
|
}
|
||||||
|
|
||||||
if(recordList.isEmpty())
|
if(recordList.isEmpty())
|
||||||
{
|
{
|
||||||
throw (new QBadRequestException("No records were found in the POST body"));
|
throw (new QBadRequestException("No records were found in the POST body"));
|
||||||
@ -851,6 +870,229 @@ public class QJavalinApiHandler
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static void bulkUpdate(Context context)
|
||||||
|
{
|
||||||
|
String version = context.pathParam("version");
|
||||||
|
String tableApiName = context.pathParam("tableName");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
|
||||||
|
String tableName = table.getName();
|
||||||
|
|
||||||
|
UpdateInput updateInput = new UpdateInput();
|
||||||
|
|
||||||
|
setupSession(context, updateInput);
|
||||||
|
QJavalinAccessLogger.logStart("bulkUpdate", logPair("table", tableName));
|
||||||
|
|
||||||
|
updateInput.setTableName(tableName);
|
||||||
|
|
||||||
|
PermissionsHelper.checkTablePermissionThrowing(updateInput, TablePermissionSubType.EDIT);
|
||||||
|
|
||||||
|
/////////////////
|
||||||
|
// build input //
|
||||||
|
/////////////////
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(!StringUtils.hasContent(context.body()))
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("Missing required PATCH body"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<QRecord> recordList = new ArrayList<>();
|
||||||
|
updateInput.setRecords(recordList);
|
||||||
|
|
||||||
|
JSONTokener jsonTokener = new JSONTokener(context.body().trim());
|
||||||
|
JSONArray jsonArray = new JSONArray(jsonTokener);
|
||||||
|
|
||||||
|
for(int i = 0; i < jsonArray.length(); i++)
|
||||||
|
{
|
||||||
|
JSONObject jsonObject = jsonArray.getJSONObject(i);
|
||||||
|
recordList.add(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(jsonTokener.more())
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("Body contained more than a single JSON array."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(recordList.isEmpty())
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("No records were found in the PATCH body"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(QBadRequestException qbre)
|
||||||
|
{
|
||||||
|
throw (qbre);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("Body could not be parsed as a JSON array: " + e.getMessage(), e));
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////
|
||||||
|
// execute! //
|
||||||
|
//////////////
|
||||||
|
UpdateAction updateAction = new UpdateAction();
|
||||||
|
UpdateOutput updateOutput = updateAction.execute(updateInput);
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
// process records to build response //
|
||||||
|
///////////////////////////////////////
|
||||||
|
List<Map<String, Serializable>> response = new ArrayList<>();
|
||||||
|
for(QRecord record : updateOutput.getRecords())
|
||||||
|
{
|
||||||
|
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
||||||
|
response.add(outputRecord);
|
||||||
|
|
||||||
|
List<String> errors = record.getErrors();
|
||||||
|
if(CollectionUtils.nullSafeHasContents(errors))
|
||||||
|
{
|
||||||
|
outputRecord.put("statusCode", HttpStatus.Code.BAD_REQUEST.getCode());
|
||||||
|
outputRecord.put("statusText", HttpStatus.Code.BAD_REQUEST.getMessage());
|
||||||
|
outputRecord.put("error", "Error updating " + table.getLabel() + ": " + StringUtils.joinWithCommasAndAnd(errors));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outputRecord.put("statusCode", HttpStatus.Code.NO_CONTENT.getCode());
|
||||||
|
outputRecord.put("statusText", HttpStatus.Code.NO_CONTENT.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QJavalinAccessLogger.logEndSuccess();
|
||||||
|
context.status(HttpStatus.Code.MULTI_STATUS.getCode());
|
||||||
|
context.result(JsonUtils.toJson(response));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
QJavalinAccessLogger.logEndFail(e);
|
||||||
|
handleException(context, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static void bulkDelete(Context context)
|
||||||
|
{
|
||||||
|
String version = context.pathParam("version");
|
||||||
|
String tableApiName = context.pathParam("tableName");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
|
||||||
|
String tableName = table.getName();
|
||||||
|
|
||||||
|
DeleteInput deleteInput = new DeleteInput();
|
||||||
|
|
||||||
|
setupSession(context, deleteInput);
|
||||||
|
QJavalinAccessLogger.logStart("bulkDelete", logPair("table", tableName));
|
||||||
|
|
||||||
|
deleteInput.setTableName(tableName);
|
||||||
|
|
||||||
|
PermissionsHelper.checkTablePermissionThrowing(deleteInput, TablePermissionSubType.DELETE);
|
||||||
|
|
||||||
|
/////////////////
|
||||||
|
// build input //
|
||||||
|
/////////////////
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(!StringUtils.hasContent(context.body()))
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("Missing required DELETE body"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<Serializable> primaryKeyList = new ArrayList<>();
|
||||||
|
deleteInput.setPrimaryKeys(primaryKeyList);
|
||||||
|
|
||||||
|
JSONTokener jsonTokener = new JSONTokener(context.body().trim());
|
||||||
|
JSONArray jsonArray = new JSONArray(jsonTokener);
|
||||||
|
|
||||||
|
for(int i = 0; i < jsonArray.length(); i++)
|
||||||
|
{
|
||||||
|
Object object = jsonArray.get(i);
|
||||||
|
if(object instanceof JSONArray || object instanceof JSONObject)
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("One or more elements inside the DELETE body JSONArray was not a primitive value"));
|
||||||
|
}
|
||||||
|
primaryKeyList.add(String.valueOf(object));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(jsonTokener.more())
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("Body contained more than a single JSON array."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(primaryKeyList.isEmpty())
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("No primary keys were found in the DELETE body"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(QBadRequestException qbre)
|
||||||
|
{
|
||||||
|
throw (qbre);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("Body could not be parsed as a JSON array: " + e.getMessage(), e));
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////
|
||||||
|
// execute! //
|
||||||
|
//////////////
|
||||||
|
DeleteAction deleteAction = new DeleteAction();
|
||||||
|
DeleteOutput deleteOutput = deleteAction.execute(deleteInput);
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
// process records to build response //
|
||||||
|
///////////////////////////////////////
|
||||||
|
List<Map<String, Serializable>> response = new ArrayList<>();
|
||||||
|
|
||||||
|
List<QRecord> recordsWithErrors = deleteOutput.getRecordsWithErrors();
|
||||||
|
Map<String, String> primaryKeyToErrorMap = new HashMap<>();
|
||||||
|
for(QRecord recordWithError : CollectionUtils.nonNullList(recordsWithErrors))
|
||||||
|
{
|
||||||
|
String primaryKey = recordWithError.getValueString(table.getPrimaryKeyField());
|
||||||
|
primaryKeyToErrorMap.put(primaryKey, StringUtils.join(", ", recordWithError.getErrors()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Serializable primaryKey : deleteInput.getPrimaryKeys())
|
||||||
|
{
|
||||||
|
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
||||||
|
response.add(outputRecord);
|
||||||
|
|
||||||
|
String primaryKeyString = ValueUtils.getValueAsString((primaryKey));
|
||||||
|
if(primaryKeyToErrorMap.containsKey(primaryKeyString))
|
||||||
|
{
|
||||||
|
outputRecord.put("statusCode", HttpStatus.Code.BAD_REQUEST.getCode());
|
||||||
|
outputRecord.put("statusText", HttpStatus.Code.BAD_REQUEST.getMessage());
|
||||||
|
outputRecord.put("error", "Error deleting " + table.getLabel() + ": " + primaryKeyToErrorMap.get(primaryKeyString));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outputRecord.put("statusCode", HttpStatus.Code.NO_CONTENT.getCode());
|
||||||
|
outputRecord.put("statusText", HttpStatus.Code.NO_CONTENT.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QJavalinAccessLogger.logEndSuccess();
|
||||||
|
context.status(HttpStatus.Code.MULTI_STATUS.getCode());
|
||||||
|
context.result(JsonUtils.toJson(response));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
QJavalinAccessLogger.logEndFail(e);
|
||||||
|
handleException(context, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -892,13 +1134,20 @@ public class QJavalinApiHandler
|
|||||||
{
|
{
|
||||||
if(!StringUtils.hasContent(context.body()))
|
if(!StringUtils.hasContent(context.body()))
|
||||||
{
|
{
|
||||||
throw (new QBadRequestException("Missing required POST body"));
|
throw (new QBadRequestException("Missing required PATCH body"));
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject jsonObject = new JSONObject(context.body());
|
JSONTokener jsonTokener = new JSONTokener(context.body().trim());
|
||||||
QRecord qRecord = QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version);
|
JSONObject jsonObject = new JSONObject(jsonTokener);
|
||||||
|
|
||||||
|
QRecord qRecord = QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version);
|
||||||
qRecord.setValue(table.getPrimaryKeyField(), primaryKey);
|
qRecord.setValue(table.getPrimaryKeyField(), primaryKey);
|
||||||
updateInput.setRecords(List.of(qRecord));
|
updateInput.setRecords(List.of(qRecord));
|
||||||
|
|
||||||
|
if(jsonTokener.more())
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("Body contained more than a single JSON object."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(QBadRequestException qbre)
|
catch(QBadRequestException qbre)
|
||||||
{
|
{
|
||||||
|
@ -29,11 +29,17 @@ import com.kingsrook.qqq.api.TestUtils;
|
|||||||
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
@ -442,17 +448,16 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
QRecord personRecord = getPersonRecord(1);
|
QRecord personRecord = getPersonRecord(1);
|
||||||
assertNull(personRecord);
|
assertNull(personRecord);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////
|
||||||
// apparently, as long as the body *starts with* json, the JSONObject constructor builds //
|
// If more than just a json object, fail //
|
||||||
// a json object out of it?? so... this in this case we expected 400, but get 201... //
|
///////////////////////////////////////////
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/")
|
response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/")
|
||||||
.body("""
|
.body("""
|
||||||
{"firstName": "Moe"}
|
{"firstName": "Moe"}
|
||||||
Not json
|
Not json
|
||||||
""")
|
""")
|
||||||
.asString();
|
.asString();
|
||||||
assertErrorResponse(HttpStatus.CREATED_201, null, response);
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Body contained more than a single JSON object", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -540,17 +545,16 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
QRecord personRecord = getPersonRecord(1);
|
QRecord personRecord = getPersonRecord(1);
|
||||||
assertNull(personRecord);
|
assertNull(personRecord);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////
|
||||||
// apparently, as long as the body *starts with* json, the JSONObject constructor builds //
|
// If more than just a json array, fail //
|
||||||
// a json object out of it?? so... this in this case we expected 400, but get 201... //
|
//////////////////////////////////////////
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||||
.body("""
|
.body("""
|
||||||
[{"firstName": "Moe"}]
|
[{"firstName": "Moe"}]
|
||||||
Not json
|
Not json
|
||||||
""")
|
""")
|
||||||
.asString();
|
.asString();
|
||||||
assertErrorResponse(HttpStatus.MULTI_STATUS_207, null, response);
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Body contained more than a single JSON array", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -618,7 +622,7 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||||
// no body
|
// no body
|
||||||
.asString();
|
.asString();
|
||||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Missing required POST body", response);
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Missing required PATCH body", response);
|
||||||
|
|
||||||
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||||
.body("""
|
.body("""
|
||||||
@ -647,17 +651,185 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
QRecord personRecord = getPersonRecord(1);
|
QRecord personRecord = getPersonRecord(1);
|
||||||
assertEquals("Mo", personRecord.getValueString("firstName"));
|
assertEquals("Mo", personRecord.getValueString("firstName"));
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// apparently, as long as the body *starts with* json, the JSONObject constructor builds //
|
|
||||||
// a json object out of it?? so... this in this case we expected 400, but get 204... //
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||||
.body("""
|
.body("""
|
||||||
{"firstName": "Moe"}
|
{"firstName": "Moe"}
|
||||||
Not json
|
Not json
|
||||||
""")
|
""")
|
||||||
.asString();
|
.asString();
|
||||||
assertErrorResponse(HttpStatus.NO_CONTENT_204, null, response);
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Body contained more than a single JSON object", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testBulkUpdate207() throws QException
|
||||||
|
{
|
||||||
|
insertSimpsons();
|
||||||
|
|
||||||
|
HttpResponse<String> response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||||
|
.body("""
|
||||||
|
[
|
||||||
|
{"id": 1, "email": "homer@simpson.com"},
|
||||||
|
{"id": 2, "email": "marge@simpson.com"},
|
||||||
|
{"email": "nobody@simpson.com"}
|
||||||
|
]
|
||||||
|
""")
|
||||||
|
.asString();
|
||||||
|
assertEquals(HttpStatus.MULTI_STATUS_207, response.getStatus());
|
||||||
|
JSONArray jsonArray = new JSONArray(response.getBody());
|
||||||
|
assertEquals(3, jsonArray.length());
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.NO_CONTENT_204, jsonArray.getJSONObject(0).getInt("statusCode"));
|
||||||
|
assertEquals(HttpStatus.NO_CONTENT_204, jsonArray.getJSONObject(1).getInt("statusCode"));
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(2).getInt("statusCode"));
|
||||||
|
assertEquals("Error updating Person: Missing value in primary key field", jsonArray.getJSONObject(2).getString("error"));
|
||||||
|
|
||||||
|
QRecord record = getPersonRecord(1);
|
||||||
|
assertEquals("homer@simpson.com", record.getValueString("email"));
|
||||||
|
|
||||||
|
record = getPersonRecord(2);
|
||||||
|
assertEquals("marge@simpson.com", record.getValueString("email"));
|
||||||
|
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("email", QCriteriaOperator.EQUALS, "nobody@simpson.com")));
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(0, queryOutput.getRecords().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testBulkUpdate400s() throws QException
|
||||||
|
{
|
||||||
|
HttpResponse<String> response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||||
|
.body("""
|
||||||
|
{"firstName": "Moe"}
|
||||||
|
""")
|
||||||
|
.asString();
|
||||||
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Body could not be parsed as a JSON array: A JSONArray text must start with '['", response);
|
||||||
|
|
||||||
|
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||||
|
// no body
|
||||||
|
.asString();
|
||||||
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Missing required PATCH body", response);
|
||||||
|
|
||||||
|
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||||
|
.body("[]")
|
||||||
|
.asString();
|
||||||
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "No records were found in the PATCH body", response);
|
||||||
|
|
||||||
|
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||||
|
.body("""
|
||||||
|
[{"firstName": "Moe", "foo": "bar"}]
|
||||||
|
""")
|
||||||
|
.asString();
|
||||||
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Request body contained 1 unrecognized field name: foo", response);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// assert nothing got updated //
|
||||||
|
////////////////////////////////
|
||||||
|
QRecord personRecord = getPersonRecord(1);
|
||||||
|
assertNull(personRecord);
|
||||||
|
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// If more than just a json array, fail //
|
||||||
|
//////////////////////////////////////////
|
||||||
|
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||||
|
.body("""
|
||||||
|
[{"firstName": "Moe"}]
|
||||||
|
Not json
|
||||||
|
""")
|
||||||
|
.asString();
|
||||||
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Body contained more than a single JSON array", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testBulkDelete207() throws QException
|
||||||
|
{
|
||||||
|
insertSimpsons();
|
||||||
|
|
||||||
|
HttpResponse<String> response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||||
|
.body("""
|
||||||
|
[ 1, 3, 5 ]
|
||||||
|
""")
|
||||||
|
.asString();
|
||||||
|
assertEquals(HttpStatus.MULTI_STATUS_207, response.getStatus());
|
||||||
|
JSONArray jsonArray = new JSONArray(response.getBody());
|
||||||
|
assertEquals(3, jsonArray.length());
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.NO_CONTENT_204, jsonArray.getJSONObject(0).getInt("statusCode"));
|
||||||
|
assertEquals(HttpStatus.NO_CONTENT_204, jsonArray.getJSONObject(1).getInt("statusCode"));
|
||||||
|
assertEquals(HttpStatus.NO_CONTENT_204, jsonArray.getJSONObject(2).getInt("statusCode"));
|
||||||
|
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(2, queryOutput.getRecords().size());
|
||||||
|
assertEquals(List.of(2, 4), queryOutput.getRecords().stream().map(r -> r.getValueInteger("id")).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testBulkDelete400s() throws QException
|
||||||
|
{
|
||||||
|
HttpResponse<String> response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||||
|
.body("""
|
||||||
|
1, 2, 3
|
||||||
|
""")
|
||||||
|
.asString();
|
||||||
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Body could not be parsed as a JSON array: A JSONArray text must start with '['", response);
|
||||||
|
|
||||||
|
response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||||
|
// no body
|
||||||
|
.asString();
|
||||||
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Missing required DELETE body", response);
|
||||||
|
|
||||||
|
response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||||
|
.body("[]")
|
||||||
|
.asString();
|
||||||
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "No primary keys were found in the DELETE body", response);
|
||||||
|
|
||||||
|
response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||||
|
.body("""
|
||||||
|
[{"id": 1}]
|
||||||
|
""")
|
||||||
|
.asString();
|
||||||
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "One or more elements inside the DELETE body JSONArray was not a primitive value", response);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// assert nothing got deleted //
|
||||||
|
////////////////////////////////
|
||||||
|
QRecord personRecord = getPersonRecord(1);
|
||||||
|
assertNull(personRecord);
|
||||||
|
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// If more than just a json array, fail //
|
||||||
|
//////////////////////////////////////////
|
||||||
|
response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||||
|
.body("""
|
||||||
|
[1,2,3]
|
||||||
|
Not json
|
||||||
|
""")
|
||||||
|
.asString();
|
||||||
|
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Body contained more than a single JSON array", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user