mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Updates for more heavy-field handling
This commit is contained in:
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"));
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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>
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
**
|
||||
|
@ -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
|
||||
**
|
||||
|
@ -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 //
|
||||
////////////////////////////////////////////
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -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"));
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
Reference in New Issue
Block a user