mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Fix merge conlifct w/ bulk insert; add warnings to single-insert; add tests re: insert warnings
This commit is contained in:
@ -382,6 +382,13 @@ public class ApiImplementation
|
|||||||
|
|
||||||
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
||||||
outputRecord.put(table.getPrimaryKeyField(), insertOutput.getRecords().get(0).getValue(table.getPrimaryKeyField()));
|
outputRecord.put(table.getPrimaryKeyField(), insertOutput.getRecords().get(0).getValue(table.getPrimaryKeyField()));
|
||||||
|
|
||||||
|
List<String> warnings = insertOutput.getRecords().get(0).getWarnings();
|
||||||
|
if(CollectionUtils.nullSafeHasContents(warnings))
|
||||||
|
{
|
||||||
|
outputRecord.put("warning", "Warning inserting " + table.getLabel() + ", some data may have been inserted: " + StringUtils.joinWithCommasAndAnd(warnings));
|
||||||
|
}
|
||||||
|
|
||||||
return (outputRecord);
|
return (outputRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,13 +463,21 @@ public class ApiImplementation
|
|||||||
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
||||||
response.add(outputRecord);
|
response.add(outputRecord);
|
||||||
|
|
||||||
List<String> errors = record.getErrors();
|
List<String> errors = record.getErrors();
|
||||||
|
List<String> warnings = record.getWarnings();
|
||||||
if(CollectionUtils.nullSafeHasContents(errors))
|
if(CollectionUtils.nullSafeHasContents(errors))
|
||||||
{
|
{
|
||||||
outputRecord.put("statusCode", HttpStatus.Code.BAD_REQUEST.getCode());
|
outputRecord.put("statusCode", HttpStatus.Code.BAD_REQUEST.getCode());
|
||||||
outputRecord.put("statusText", HttpStatus.Code.BAD_REQUEST.getMessage());
|
outputRecord.put("statusText", HttpStatus.Code.BAD_REQUEST.getMessage());
|
||||||
outputRecord.put("error", "Error inserting " + table.getLabel() + ": " + StringUtils.joinWithCommasAndAnd(errors));
|
outputRecord.put("error", "Error inserting " + table.getLabel() + ": " + StringUtils.joinWithCommasAndAnd(errors));
|
||||||
}
|
}
|
||||||
|
else if(CollectionUtils.nullSafeHasContents(warnings))
|
||||||
|
{
|
||||||
|
outputRecord.put("statusCode", HttpStatus.Code.CREATED.getCode());
|
||||||
|
outputRecord.put("statusText", HttpStatus.Code.CREATED.getMessage());
|
||||||
|
outputRecord.put("warning", "Warning inserting " + table.getLabel() + ", some data may have been inserted: " + StringUtils.joinWithCommasAndAnd(warnings));
|
||||||
|
outputRecord.put(table.getPrimaryKeyField(), record.getValue(table.getPrimaryKeyField()));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
outputRecord.put("statusCode", HttpStatus.Code.CREATED.getCode());
|
outputRecord.put("statusCode", HttpStatus.Code.CREATED.getCode());
|
||||||
|
@ -902,93 +902,6 @@ public class QJavalinApiHandler
|
|||||||
|
|
||||||
List<Map<String, Serializable>> response = ApiImplementation.bulkInsert(apiInstanceMetaData, version, tableApiName, context.body());
|
List<Map<String, Serializable>> response = ApiImplementation.bulkInsert(apiInstanceMetaData, version, tableApiName, context.body());
|
||||||
|
|
||||||
setupSession(context, insertInput, version, apiInstanceMetaData);
|
|
||||||
QJavalinAccessLogger.logStart("apiBulkInsert", logPair("table", tableName));
|
|
||||||
|
|
||||||
insertInput.setTableName(tableName);
|
|
||||||
|
|
||||||
PermissionsHelper.checkTablePermissionThrowing(insertInput, TablePermissionSubType.INSERT);
|
|
||||||
|
|
||||||
/////////////////
|
|
||||||
// build input //
|
|
||||||
/////////////////
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(!StringUtils.hasContent(context.body()))
|
|
||||||
{
|
|
||||||
throw (new QBadRequestException("Missing required POST body"));
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<QRecord> recordList = new ArrayList<>();
|
|
||||||
insertInput.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, apiInstanceMetaData.getName(), version, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 POST 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! //
|
|
||||||
//////////////
|
|
||||||
InsertAction insertAction = new InsertAction();
|
|
||||||
InsertOutput insertOutput = insertAction.execute(insertInput);
|
|
||||||
|
|
||||||
///////////////////////////////////////
|
|
||||||
// process records to build response //
|
|
||||||
///////////////////////////////////////
|
|
||||||
List<Map<String, Serializable>> response = new ArrayList<>();
|
|
||||||
for(QRecord record : insertOutput.getRecords())
|
|
||||||
{
|
|
||||||
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
|
||||||
response.add(outputRecord);
|
|
||||||
|
|
||||||
List<String> errors = record.getErrors();
|
|
||||||
List<String> warnings = record.getWarnings();
|
|
||||||
if(CollectionUtils.nullSafeHasContents(errors))
|
|
||||||
{
|
|
||||||
outputRecord.put("statusCode", HttpStatus.Code.BAD_REQUEST.getCode());
|
|
||||||
outputRecord.put("statusText", HttpStatus.Code.BAD_REQUEST.getMessage());
|
|
||||||
outputRecord.put("error", "Error inserting " + table.getLabel() + ": " + StringUtils.joinWithCommasAndAnd(errors));
|
|
||||||
}
|
|
||||||
else if(CollectionUtils.nullSafeHasContents(warnings))
|
|
||||||
{
|
|
||||||
outputRecord.put("statusCode", HttpStatus.Code.BAD_REQUEST.getCode());
|
|
||||||
outputRecord.put("statusText", HttpStatus.Code.BAD_REQUEST.getMessage());
|
|
||||||
outputRecord.put("error", "Warning inserting " + table.getLabel() + ", some data may have been inserted: " + StringUtils.joinWithCommasAndAnd(warnings));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
outputRecord.put("statusCode", HttpStatus.Code.CREATED.getCode());
|
|
||||||
outputRecord.put("statusText", HttpStatus.Code.CREATED.getMessage());
|
|
||||||
outputRecord.put(table.getPrimaryKeyField(), record.getValue(table.getPrimaryKeyField()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QJavalinAccessLogger.logEndSuccess(logPair("recordCount", insertInput.getRecords().size()));
|
|
||||||
|
|
||||||
context.status(HttpStatus.Code.MULTI_STATUS.getCode());
|
context.status(HttpStatus.Code.MULTI_STATUS.getCode());
|
||||||
String resultString = JsonUtils.toJson(response);
|
String resultString = JsonUtils.toJson(response);
|
||||||
context.result(resultString);
|
context.result(resultString);
|
||||||
|
@ -31,6 +31,8 @@ import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
|||||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
|
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
|
||||||
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
||||||
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
|
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreInsertCustomizer;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||||
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.insert.InsertInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
@ -40,6 +42,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0AuthenticationMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0AuthenticationMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
@ -50,6 +54,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -136,6 +141,33 @@ public class TestUtils
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static class PersonPreInsertCustomizer extends AbstractPreInsertCustomizer
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public List<QRecord> apply(List<QRecord> records)
|
||||||
|
{
|
||||||
|
for(QRecord record : CollectionUtils.nonNullList(records))
|
||||||
|
{
|
||||||
|
if(!record.getValueString("firstName").matches(".*[a-z].*"))
|
||||||
|
{
|
||||||
|
record.addWarning("First name does not contain any letters...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (records);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Define the 'person' table used in standard tests.
|
** Define the 'person' table used in standard tests.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -161,6 +193,8 @@ public class TestUtils
|
|||||||
.withField(new QFieldMetaData("cost", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY))
|
.withField(new QFieldMetaData("cost", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY))
|
||||||
.withField(new QFieldMetaData("price", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY));
|
.withField(new QFieldMetaData("price", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY));
|
||||||
|
|
||||||
|
table.withCustomizer(TableCustomizers.PRE_INSERT_RECORD.getRole(), new QCodeReference(PersonPreInsertCustomizer.class, QCodeUsage.CUSTOMIZER));
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// make some changes to this table in the "main" api (but leave it like the backend in the ALTERNATIVE_API_NAME) //
|
// make some changes to this table in the "main" api (but leave it like the backend in the ALTERNATIVE_API_NAME) //
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -556,6 +556,25 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testInsert201WithWarning() throws QException
|
||||||
|
{
|
||||||
|
HttpResponse<String> response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/")
|
||||||
|
.body("""
|
||||||
|
{"firstName": "--"}
|
||||||
|
""")
|
||||||
|
.asString();
|
||||||
|
assertEquals(HttpStatus.CREATED_201, response.getStatus());
|
||||||
|
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||||
|
assertEquals(1, jsonObject.getInt("id"));
|
||||||
|
assertTrue(jsonObject.has("warning"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -677,13 +696,14 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
{"firstName": "Moe", "email": "moe@moes.com"},
|
{"firstName": "Moe", "email": "moe@moes.com"},
|
||||||
{"firstName": "Barney", "email": "barney@moes.com"},
|
{"firstName": "Barney", "email": "barney@moes.com"},
|
||||||
{"firstName": "CM", "email": "boss@snpp.com"},
|
{"firstName": "CM", "email": "boss@snpp.com"},
|
||||||
{"firstName": "Waylon", "email": "boss@snpp.com"}
|
{"firstName": "Waylon", "email": "boss@snpp.com"},
|
||||||
|
{"firstName": "--", "email": "dashdash@simpsons.com"}
|
||||||
]
|
]
|
||||||
""")
|
""")
|
||||||
.asString();
|
.asString();
|
||||||
assertEquals(HttpStatus.MULTI_STATUS_207, response.getStatus());
|
assertEquals(HttpStatus.MULTI_STATUS_207, response.getStatus());
|
||||||
JSONArray jsonArray = new JSONArray(response.getBody());
|
JSONArray jsonArray = new JSONArray(response.getBody());
|
||||||
assertEquals(4, jsonArray.length());
|
assertEquals(5, jsonArray.length());
|
||||||
|
|
||||||
assertEquals(HttpStatus.CREATED_201, jsonArray.getJSONObject(0).getInt("statusCode"));
|
assertEquals(HttpStatus.CREATED_201, jsonArray.getJSONObject(0).getInt("statusCode"));
|
||||||
assertEquals(1, jsonArray.getJSONObject(0).getInt("id"));
|
assertEquals(1, jsonArray.getJSONObject(0).getInt("id"));
|
||||||
@ -697,6 +717,10 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(3).getInt("statusCode"));
|
assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(3).getInt("statusCode"));
|
||||||
assertEquals("Error inserting Person: Another record already exists with this Email", jsonArray.getJSONObject(3).getString("error"));
|
assertEquals("Error inserting Person: Another record already exists with this Email", jsonArray.getJSONObject(3).getString("error"));
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.CREATED_201, jsonArray.getJSONObject(4).getInt("statusCode"));
|
||||||
|
assertEquals(4, jsonArray.getJSONObject(4).getInt("id"));
|
||||||
|
assertTrue(jsonArray.getJSONObject(4).has("warning"));
|
||||||
|
|
||||||
QRecord record = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
|
QRecord record = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
|
||||||
assertEquals("Moe", record.getValueString("firstName"));
|
assertEquals("Moe", record.getValueString("firstName"));
|
||||||
|
|
||||||
@ -707,6 +731,9 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
assertEquals("CM", record.getValueString("firstName"));
|
assertEquals("CM", record.getValueString("firstName"));
|
||||||
|
|
||||||
record = getRecord(TestUtils.TABLE_NAME_PERSON, 4);
|
record = getRecord(TestUtils.TABLE_NAME_PERSON, 4);
|
||||||
|
assertEquals("--", record.getValueString("firstName"));
|
||||||
|
|
||||||
|
record = getRecord(TestUtils.TABLE_NAME_PERSON, 5);
|
||||||
assertNull(record);
|
assertNull(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user