Updates for more heavy-field handling

This commit is contained in:
2023-05-30 10:13:04 -05:00
parent 8235bb2bb7
commit a75530b466
13 changed files with 362 additions and 80 deletions

View File

@ -187,32 +187,57 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
continue;
}
String formattedValue = getFormattedValueForAuditDetail(record, fieldName, field, value);
detailRecord = new QRecord().withValue("message", "Set " + field.getLabel() + " to " + formattedValue);
detailRecord.withValue("newValue", formattedValue);
if(field.getType().equals(QFieldType.BLOB))
{
detailRecord = new QRecord().withValue("message", "Set " + field.getLabel());
}
else
{
String formattedValue = getFormattedValueForAuditDetail(record, fieldName, field, value);
detailRecord = new QRecord().withValue("message", "Set " + field.getLabel() + " to " + formattedValue);
detailRecord.withValue("newValue", formattedValue);
}
}
else
{
if(!Objects.equals(oldValue, value))
{
String formattedValue = getFormattedValueForAuditDetail(record, fieldName, field, value);
String formattedOldValue = getFormattedValueForAuditDetail(oldRecord, fieldName, field, oldValue);
if(oldValue == null)
if(field.getType().equals(QFieldType.BLOB))
{
detailRecord = new QRecord().withValue("message", "Set " + field.getLabel() + " to " + formatFormattedValueForDetailMessage(field, formattedValue));
detailRecord.withValue("newValue", formattedValue);
}
else if(value == null)
{
detailRecord = new QRecord().withValue("message", "Removed " + formatFormattedValueForDetailMessage(field, formattedOldValue) + " from " + field.getLabel());
detailRecord.withValue("oldValue", formattedOldValue);
if(oldValue == null)
{
detailRecord = new QRecord().withValue("message", "Set " + field.getLabel());
}
else if(value == null)
{
detailRecord = new QRecord().withValue("message", "Removed " + field.getLabel());
}
else
{
detailRecord = new QRecord().withValue("message", "Changed " + field.getLabel());
}
}
else
{
detailRecord = new QRecord().withValue("message", "Changed " + field.getLabel() + " from " + formatFormattedValueForDetailMessage(field, formattedOldValue) + " to " + formatFormattedValueForDetailMessage(field, formattedValue));
detailRecord.withValue("oldValue", formattedOldValue);
detailRecord.withValue("newValue", formattedValue);
String formattedValue = getFormattedValueForAuditDetail(record, fieldName, field, value);
String formattedOldValue = getFormattedValueForAuditDetail(oldRecord, fieldName, field, oldValue);
if(oldValue == null)
{
detailRecord = new QRecord().withValue("message", "Set " + field.getLabel() + " to " + formatFormattedValueForDetailMessage(field, formattedValue));
detailRecord.withValue("newValue", formattedValue);
}
else if(value == null)
{
detailRecord = new QRecord().withValue("message", "Removed " + formatFormattedValueForDetailMessage(field, formattedOldValue) + " from " + field.getLabel());
detailRecord.withValue("oldValue", formattedOldValue);
}
else
{
detailRecord = new QRecord().withValue("message", "Changed " + field.getLabel() + " from " + formatFormattedValueForDetailMessage(field, formattedOldValue) + " to " + formatFormattedValueForDetailMessage(field, formattedValue));
detailRecord.withValue("oldValue", formattedOldValue);
detailRecord.withValue("newValue", formattedValue);
}
}
}
}

View File

@ -386,13 +386,22 @@ public class ExportAction
fieldList = new ArrayList<>(table.getFields().values());
}
//////////////////////////////////////////
// add fields for possible value labels //
//////////////////////////////////////////
List<QFieldMetaData> returnList = new ArrayList<>();
for(QFieldMetaData field : fieldList)
{
/////////////////////////////////////////////////////////////////////////////////////////
// skip heavy fields. they aren't fetched, and we generally think we don't want them. //
/////////////////////////////////////////////////////////////////////////////////////////
if(field.getIsHeavy())
{
continue;
}
returnList.add(field);
//////////////////////////////////////////
// add fields for possible value labels //
//////////////////////////////////////////
if(StringUtils.hasContent(field.getPossibleValueSourceName()))
{
returnList.add(new QFieldMetaData(field.getName() + ":possibleValueLabel", QFieldType.STRING).withLabel(field.getLabel() + " Name"));

View File

@ -81,6 +81,16 @@ public class QueryAction
{
ActionHelper.validateSession(queryInput);
if(queryInput.getTableName() == null)
{
throw (new QException("Table name was not specified in query input"));
}
if(queryInput.getTable() == null)
{
throw (new QException("A table named [" + queryInput.getTableName() + "] was not found in the active QInstance"));
}
postQueryRecordCustomizer = QCodeLoader.getTableCustomizer(AbstractPostQueryCustomizer.class, queryInput.getTable(), TableCustomizers.POST_QUERY_RECORD.getRole());
this.queryInput = queryInput;

View File

@ -735,7 +735,7 @@ public class QInstanceEnricher
QFrontendStepMetaData uploadScreen = new QFrontendStepMetaData()
.withName("upload")
.withLabel("Upload File")
.withFormField(new QFieldMetaData("theFile", QFieldType.BLOB).withIsRequired(true))
.withFormField(new QFieldMetaData("theFile", QFieldType.BLOB).withLabel(table.getLabel() + " File").withIsRequired(true))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("previewText", "file upload instructions")

View File

@ -69,7 +69,11 @@ public class QRecord implements Serializable
private Map<String, List<QRecord>> associatedRecords = new HashMap<>();
public static final String BACKEND_DETAILS_TYPE_JSON_SOURCE_OBJECT = "jsonSourceObject";
////////////////////////////////////////////////
// well-known keys for the backendDetails map //
////////////////////////////////////////////////
public static final String BACKEND_DETAILS_TYPE_JSON_SOURCE_OBJECT = "jsonSourceObject"; // String of JSON
public static final String BACKEND_DETAILS_TYPE_HEAVY_FIELD_LENGTHS = "heavyFieldLengths"; // Map<fieldName, length>

View File

@ -25,6 +25,8 @@ package com.kingsrook.qqq.backend.core.model.metadata.fields;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.utils.Pair;
@ -116,6 +118,22 @@ public class FieldAdornment
/*******************************************************************************
**
*******************************************************************************/
@JsonIgnore
public Optional<Serializable> getValue(String key)
{
if(key != null && values != null)
{
return (Optional.ofNullable(values.get(key)));
}
return (Optional.empty());
}
/*******************************************************************************
** Setter for values
**

View File

@ -31,6 +31,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.github.hervian.reflection.Fun;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
@ -534,6 +535,29 @@ public class QFieldMetaData implements Cloneable
/*******************************************************************************
** Getter for adornments
**
*******************************************************************************/
@JsonIgnore
public Optional<FieldAdornment> getAdornment(AdornmentType adornmentType)
{
if(adornmentType != null && adornments != null)
{
for(FieldAdornment adornment : adornments)
{
if(adornmentType.equals(adornment.getType()))
{
return Optional.of((adornment));
}
}
}
return (Optional.empty());
}
/*******************************************************************************
** Setter for adornments
**

View File

@ -134,6 +134,11 @@ public class ColumnStatsStep implements BackendStep
throw (new QException("Could not find field by name: " + fieldName));
}
if(field.getType().equals(QFieldType.BLOB))
{
throw (new QException("Column stats are not supported for this field's data type."));
}
////////////////////////////////////////////
// do a count query grouped by this field //
////////////////////////////////////////////

View File

@ -29,8 +29,10 @@ import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
import com.kingsrook.qqq.backend.core.exceptions.QException;
@ -43,8 +45,10 @@ 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.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.Pair;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
@ -112,9 +116,7 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
////////////////////////////////////////////////////////////////////////////
// build the list of fields that will be processed in the result-set loop //
////////////////////////////////////////////////////////////////////////////
List<QFieldMetaData> fieldList = new ArrayList<>(table.getFields().values().stream()
.filter(field -> filterOutHeavyFieldsIfNeeded(field, queryInput.getShouldFetchHeavyFields()))
.toList());
List<QFieldMetaData> fieldList = new ArrayList<>(table.getFields().values().stream().toList());
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryInput.getQueryJoins()))
{
if(queryJoin.getSelect())
@ -123,10 +125,7 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
String tableNameOrAlias = queryJoin.getJoinTableOrItsAlias();
for(QFieldMetaData joinField : joinTable.getFields().values())
{
if(filterOutHeavyFieldsIfNeeded(joinField, queryInput.getShouldFetchHeavyFields()))
{
fieldList.add(joinField.clone().withName(tableNameOrAlias + "." + joinField.getName()));
}
fieldList.add(joinField.clone().withName(tableNameOrAlias + "." + joinField.getName()));
}
}
}
@ -153,9 +152,22 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
for(int i = 1; i <= metaData.getColumnCount(); i++)
{
QFieldMetaData qFieldMetaData = fieldList.get(i - 1);
Serializable value = getFieldValueFromResultSet(qFieldMetaData, resultSet, i);
values.put(qFieldMetaData.getName(), value);
QFieldMetaData field = fieldList.get(i - 1);
if(!queryInput.getShouldFetchHeavyFields() && field.getIsHeavy())
{
///////////////////////////////////////////////////////////////////////////////////
// if this is a non-fetched heavy field (e.g., we just fetched its length), then //
// get the value here as an INTEGER, not a BLOB or whatever the field would be //
///////////////////////////////////////////////////////////////////////////////////
Serializable fieldLength = getFieldValueFromResultSet(QFieldType.INTEGER, resultSet, i);
setHeavyFieldLengthInRecordBackendDetails(record, field, fieldLength);
}
else
{
Serializable value = getFieldValueFromResultSet(field, resultSet, i);
values.put(field.getName(), value);
}
}
queryOutput.addRecord(record);
@ -195,6 +207,27 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("unchecked")
private static void setHeavyFieldLengthInRecordBackendDetails(QRecord record, QFieldMetaData field, Serializable fieldLength)
{
if(record.getBackendDetails() == null)
{
record.setBackendDetails(new HashMap<>());
}
if(record.getBackendDetail(QRecord.BACKEND_DETAILS_TYPE_HEAVY_FIELD_LENGTHS) == null)
{
record.addBackendDetail(QRecord.BACKEND_DETAILS_TYPE_HEAVY_FIELD_LENGTHS, new HashMap<>());
}
((Map<String, Serializable>) record.getBackendDetail(QRecord.BACKEND_DETAILS_TYPE_HEAVY_FIELD_LENGTHS)).put(field.getName(), fieldLength);
}
/*******************************************************************************
**
*******************************************************************************/
@ -210,8 +243,8 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
List<QFieldMetaData> fieldList = new ArrayList<>(table.getFields().values());
String columns = fieldList.stream()
.filter(field -> filterOutHeavyFieldsIfNeeded(field, queryInput.getShouldFetchHeavyFields()))
.map(field -> escapeIdentifier(tableName) + "." + escapeIdentifier(getColumnName(field)))
.map(field -> Pair.of(field, escapeIdentifier(tableName) + "." + escapeIdentifier(getColumnName(field))))
.map(pair -> wrapHeavyFieldsWithLengthFunctionIfNeeded(pair, queryInput.getShouldFetchHeavyFields()))
.collect(Collectors.joining(", "));
StringBuilder rs = new StringBuilder(clausePrefix).append(columns);
@ -229,7 +262,8 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
List<QFieldMetaData> joinFieldList = new ArrayList<>(joinTable.getFields().values());
String joinColumns = joinFieldList.stream()
.filter(field -> filterOutHeavyFieldsIfNeeded(field, queryInput.getShouldFetchHeavyFields()))
.map(field -> escapeIdentifier(tableNameOrAlias) + "." + escapeIdentifier(getColumnName(field)))
.map(field -> Pair.of(field, escapeIdentifier(tableNameOrAlias) + "." + escapeIdentifier(getColumnName(field))))
.map(pair -> wrapHeavyFieldsWithLengthFunctionIfNeeded(pair, queryInput.getShouldFetchHeavyFields()))
.collect(Collectors.joining(", "));
rs.append(", ").append(joinColumns);
}
@ -254,6 +288,24 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
/*******************************************************************************
** if we're not fetching heavy fields, instead just get their length. this
** method wraps the field 'sql name' (e.g., column_name or table_name.column_name)
** with the LENGTH() function, if needed.
*******************************************************************************/
private String wrapHeavyFieldsWithLengthFunctionIfNeeded(Pair<QFieldMetaData, String> fieldAndSqlName, boolean shouldFetchHeavyFields)
{
QFieldMetaData field = fieldAndSqlName.getA();
String sqlName = fieldAndSqlName.getB();
if(!shouldFetchHeavyFields && field.getIsHeavy())
{
return ("LENGTH(" + sqlName + ")");
}
return (sqlName);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -167,6 +167,7 @@ public class TestUtils
.withField(new QFieldMetaData("isEmployed", QFieldType.BOOLEAN).withBackendName("is_employed"))
.withField(new QFieldMetaData("annualSalary", QFieldType.DECIMAL).withBackendName("annual_salary"))
.withField(new QFieldMetaData("daysWorked", QFieldType.INTEGER).withBackendName("days_worked"))
.withField(new QFieldMetaData("homeTown", QFieldType.STRING).withBackendName("home_town"))
.withBackendDetails(new RDBMSTableBackendDetails()
.withTableName("person"));
}

View File

@ -22,10 +22,12 @@
package com.kingsrook.qqq.backend.module.rdbms.actions;
import java.io.Serializable;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@ -1663,4 +1665,35 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
.hasSize(1);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testHeavyFields() throws QException
{
//////////////////////////////////////////////////////////
// set homeTown field as heavy - so it won't be fetched //
//////////////////////////////////////////////////////////
QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON)
.getField("homeTown")
.withIsHeavy(true);
QueryInput queryInput = new QueryInput();
queryInput.setTableName(TestUtils.TABLE_NAME_PERSON);
List<QRecord> records = new QueryAction().execute(queryInput).getRecords();
assertThat(records).describedAs("No records should have the heavy homeTown field set").noneMatch(r -> r.getValue("homeTown") != null);
assertThat(records).describedAs("Some records should have a homeTown length backend detail set").anyMatch(r -> ((Map<String, Serializable>) r.getBackendDetail(QRecord.BACKEND_DETAILS_TYPE_HEAVY_FIELD_LENGTHS)).get("homeTown") != null);
assertThat(records).describedAs("Some records should have a null homeTown length backend").anyMatch(r -> ((Map<String, Serializable>) r.getBackendDetail(QRecord.BACKEND_DETAILS_TYPE_HEAVY_FIELD_LENGTHS)).get("homeTown") == null);
//////////////////////////////////////////////
// re-do the query, requesting heavy fields //
//////////////////////////////////////////////
queryInput.setShouldFetchHeavyFields(true);
records = new QueryAction().execute(queryInput).getRecords();
assertThat(records).describedAs("Some records should have the heavy homeTown field set when heavies are requested").anyMatch(r -> r.getValue("homeTown") != null);
}
}

View File

@ -32,14 +32,15 @@ CREATE TABLE person
email VARCHAR(250) NOT NULL,
is_employed BOOLEAN,
annual_salary DECIMAL(12,2),
days_worked INTEGER
days_worked INTEGER,
home_town VARCHAR(80)
);
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com', 1, 25000, 27);
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (2, 'James', 'Maes', '1980-05-15', 'jmaes@mmltholdings.com', 1, 26000, 124);
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com', 0, null, 0);
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com', 1, 30000, 99);
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com', 1, 1000000, 232);
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked, home_town) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com', 1, 25000, 27, 'Chester');
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked, home_town) VALUES (2, 'James', 'Maes', '1980-05-15', 'jmaes@mmltholdings.com', 1, 26000, 124, 'Chester');
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked, home_town) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com', 0, null, 0, 'Decatur');
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked, home_town) VALUES (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com', 1, 30000, 99, 'Texas');
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked, home_town) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com', 1, 1000000, 232, null);
DROP TABLE IF EXISTS personal_id_card;
CREATE TABLE personal_id_card

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.javalin;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Serializable;
@ -116,6 +117,7 @@ import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
import io.javalin.Javalin;
import io.javalin.apibuilder.EndpointGroup;
import io.javalin.http.Context;
import io.javalin.http.UploadedFile;
import org.apache.commons.io.FileUtils;
import org.eclipse.jetty.http.HttpStatus;
import org.json.JSONArray;
@ -619,38 +621,17 @@ public class QJavalinImplementation
updateInput.setTableName(table);
PermissionsHelper.checkTablePermissionThrowing(updateInput, TablePermissionSubType.EDIT);
QTableMetaData tableMetaData = qInstance.getTable(table);
QJavalinAccessLogger.logStart("update", logPair("table", table), logPair("primaryKey", primaryKey));
List<QRecord> recordList = new ArrayList<>();
QRecord record = new QRecord();
record.setTableName(table);
recordList.add(record);
Map<?, ?> map = context.bodyAsClass(Map.class);
for(Map.Entry<?, ?> entry : map.entrySet())
{
String fieldName = ValueUtils.getValueAsString(entry.getKey());
Object value = entry.getValue();
if(StringUtils.hasContent(String.valueOf(value)))
{
record.setValue(fieldName, (Serializable) value);
}
else if("".equals(value))
{
///////////////////////////////////////////////////////////////////////////////////////////////////
// if frontend sent us an empty string - put a null in the record's value map. //
// this could potentially be changed to be type-specific (e.g., store an empty-string for STRING //
// fields, but null for INTEGER, etc) - but, who really wants empty-string in database anyway? //
///////////////////////////////////////////////////////////////////////////////////////////////////
record.setValue(fieldName, null);
}
}
QTableMetaData tableMetaData = qInstance.getTable(table);
record.setValue(tableMetaData.getPrimaryKeyField(), primaryKey);
QJavalinAccessLogger.logStart("update", logPair("table", table), logPair("primaryKey", primaryKey));
setRecordValuesForInsertOrUpdate(context, tableMetaData, record);
updateInput.setRecords(recordList);
UpdateAction updateAction = new UpdateAction();
@ -668,6 +649,87 @@ public class QJavalinImplementation
/*******************************************************************************
**
*******************************************************************************/
private static void setRecordValuesForInsertOrUpdate(Context context, QTableMetaData tableMetaData, QRecord record) throws IOException
{
/////////////////////////
// process form params //
/////////////////////////
for(Map.Entry<String, List<String>> formParam : context.formParamMap().entrySet())
{
String fieldName = formParam.getKey();
List<String> values = formParam.getValue();
if(CollectionUtils.nullSafeHasContents(values))
{
String value = values.get(0);
if(StringUtils.hasContent(value))
{
record.setValue(fieldName, value);
}
else
{
record.setValue(fieldName, null);
}
}
else
{
// is this ever hit?
record.setValue(fieldName, null);
}
}
////////////////////////////
// process uploaded files //
////////////////////////////
for(Map.Entry<String, List<UploadedFile>> entry : CollectionUtils.nonNullMap(context.uploadedFileMap()).entrySet())
{
String fieldName = entry.getKey();
List<UploadedFile> uploadedFiles = entry.getValue();
if(uploadedFiles.size() > 0)
{
UploadedFile uploadedFile = uploadedFiles.get(0);
try(InputStream content = uploadedFile.content())
{
record.setValue(fieldName, content.readAllBytes());
}
QFieldMetaData blobField = tableMetaData.getField(fieldName);
blobField.getAdornment(AdornmentType.FILE_DOWNLOAD).ifPresent(adornment ->
{
adornment.getValue(AdornmentType.FileDownloadValues.FILE_NAME_FIELD).ifPresent(fileNameFieldName ->
{
record.setValue(ValueUtils.getValueAsString(fileNameFieldName), uploadedFile.filename());
});
});
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if the record has any blob fields, and we're clearing them out (present in the values list, and set to null), //
// and they have a file-name field associated with them, then also clear out that file-name field //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for(QFieldMetaData field : tableMetaData.getFields().values())
{
if(field.getType().equals(QFieldType.BLOB))
{
field.getAdornment(AdornmentType.FILE_DOWNLOAD).ifPresent(adornment ->
{
adornment.getValue(AdornmentType.FileDownloadValues.FILE_NAME_FIELD).ifPresent(fileNameFieldName ->
{
if(record.getValues().containsKey(field.getName()) && record.getValue(field.getName()) == null)
{
record.setValue(ValueUtils.getValueAsString(fileNameFieldName), null);
}
});
});
}
}
}
/*******************************************************************************
**
*******************************************************************************/
@ -682,20 +744,13 @@ public class QJavalinImplementation
QJavalinAccessLogger.logStart("insert", logPair("table", tableName));
PermissionsHelper.checkTablePermissionThrowing(insertInput, TablePermissionSubType.INSERT);
QTableMetaData tableMetaData = qInstance.getTable(tableName);
List<QRecord> recordList = new ArrayList<>();
QRecord record = new QRecord();
record.setTableName(tableName);
recordList.add(record);
Map<?, ?> map = context.bodyAsClass(Map.class);
for(Map.Entry<?, ?> entry : map.entrySet())
{
if(StringUtils.hasContent(String.valueOf(entry.getValue())))
{
record.setValue(String.valueOf(entry.getKey()), (Serializable) entry.getValue());
}
}
setRecordValuesForInsertOrUpdate(context, tableMetaData, record);
insertInput.setRecords(recordList);
InsertAction insertAction = new InsertAction();
@ -703,14 +758,14 @@ public class QJavalinImplementation
if(CollectionUtils.nullSafeHasContents(insertOutput.getRecords().get(0).getErrors()))
{
throw (new QUserFacingException("Error inserting " + qInstance.getTable(tableName).getLabel() + ": " + insertOutput.getRecords().get(0).getErrors().get(0)));
throw (new QUserFacingException("Error inserting " + tableMetaData.getLabel() + ": " + insertOutput.getRecords().get(0).getErrors().get(0)));
}
if(CollectionUtils.nullSafeHasContents(insertOutput.getRecords().get(0).getWarnings()))
{
throw (new QUserFacingException("Warning inserting " + qInstance.getTable(tableName).getLabel() + ": " + insertOutput.getRecords().get(0).getWarnings().get(0)));
throw (new QUserFacingException("Warning inserting " + tableMetaData.getLabel() + ": " + insertOutput.getRecords().get(0).getWarnings().get(0)));
}
QJavalinAccessLogger.logEndSuccess(logPair("primaryKey", () -> (insertOutput.getRecords().get(0).getValue(qInstance.getTable(tableName).getPrimaryKeyField()))));
QJavalinAccessLogger.logEndSuccess(logPair("primaryKey", () -> (insertOutput.getRecords().get(0).getValue(tableMetaData.getPrimaryKeyField()))));
context.result(JsonUtils.toJson(insertOutput));
}
catch(Exception e)
@ -795,7 +850,7 @@ public class QJavalinImplementation
// - tableLabel primaryKey fieldLabel //
// - and - if the FILE_DOWNLOAD adornment had a DEFAULT_EXTENSION, then it gets added (preceded by a dot) //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Optional<FieldAdornment> fileDownloadAdornment = field.getAdornments().stream().filter(a -> a.getType().equals(AdornmentType.FILE_DOWNLOAD)).findFirst();
Optional<FieldAdornment> fileDownloadAdornment = field.getAdornment(AdornmentType.FILE_DOWNLOAD);
Map<String, Serializable> adornmentValues = Collections.emptyMap();
if(fileDownloadAdornment.isPresent())
@ -809,6 +864,11 @@ public class QJavalinImplementation
for(QRecord record : records)
{
if(!doesFieldHaveValue(field, record))
{
continue;
}
Serializable primaryKey = record.getValue(table.getPrimaryKeyField());
String fileName = null;
@ -826,7 +886,8 @@ public class QJavalinImplementation
{
@SuppressWarnings("unchecked") // instance validation should make this safe!
List<String> fileNameFormatFields = (List<String>) adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT_FIELDS);
fileName = QValueFormatter.formatStringWithValues(fileNameFormat, fileNameFormatFields);
List<String> values = fileNameFormatFields.stream().map(f -> ValueUtils.getValueAsString(record.getValue(f))).toList();
fileName = QValueFormatter.formatStringWithValues(fileNameFormat, values);
}
}
@ -855,6 +916,45 @@ public class QJavalinImplementation
/*******************************************************************************
**
*******************************************************************************/
private static boolean doesFieldHaveValue(QFieldMetaData field, QRecord record)
{
boolean fieldHasValue = false;
try
{
if(record.getValue(field.getName()) != null)
{
fieldHasValue = true;
}
else if(field.getIsHeavy())
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// heavy fields that weren't fetched - they should have a backend-detail specifying their length (or null if null) //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Map<String, Serializable> heavyFieldLengths = (Map<String, Serializable>) record.getBackendDetail(QRecord.BACKEND_DETAILS_TYPE_HEAVY_FIELD_LENGTHS);
if(heavyFieldLengths != null)
{
Integer fieldLength = ValueUtils.getValueAsInteger(heavyFieldLengths.get(field.getName()));
if(fieldLength != null && fieldLength > 0)
{
fieldHasValue = true;
}
}
}
}
catch(Exception e)
{
LOG.info("Error checking if field has value", e, logPair("fieldName", field.getName()), logPair("record", record));
}
return fieldHasValue;
}
/*******************************************************************************
**
*******************************************************************************/