QQQ-21 change Update action to use batch, and single-query w/ IN list for same-updates

This commit is contained in:
2022-07-12 12:36:31 -05:00
parent 07def45086
commit b94431b52a
11 changed files with 554 additions and 130 deletions

View File

@ -42,7 +42,7 @@ jobs:
executor: java17
steps:
- run_maven:
maven_subcommand: test
maven_subcommand: verify
- slack/notify:
event: fail

1
.gitignore vendored
View File

@ -27,3 +27,4 @@ target/
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
.DS_Store

View File

@ -181,8 +181,8 @@
</module>
-->
<module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<!--
<module name="VariableDeclarationUsageDistance"/>
<module name="CustomImportOrder">
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="true"/>

View File

@ -97,7 +97,7 @@ public abstract class AbstractRDBMSAction
** Handle obvious problems with values - like empty string for integer should be null.
**
*******************************************************************************/
protected Serializable scrubValue(QFieldMetaData field, Serializable value)
protected Serializable scrubValue(QFieldMetaData field, Serializable value, boolean isInsert)
{
if("".equals(value))
{
@ -111,10 +111,13 @@ public abstract class AbstractRDBMSAction
//////////////////////////////////////////////////////
// todo - let this come from something in the field //
//////////////////////////////////////////////////////
if(value == null && (field.getName().equals("createDate") || field.getName().equals("modifyDate")))
if(value == null)
{
if((isInsert && field.getName().equals("createDate")) || field.getName().equals("modifyDate"))
{
value = OffsetDateTime.now();
}
}
return (value);
}

View File

@ -85,7 +85,7 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte
for(QFieldMetaData field : insertableFields)
{
Serializable value = record.getValue(field.getName());
value = scrubValue(field, value);
value = scrubValue(field, value, true);
params.add(value);
}

View File

@ -24,8 +24,11 @@ package com.kingsrook.qqq.backend.module.rdbms.actions;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest;
@ -34,11 +37,19 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
import com.kingsrook.qqq.backend.core.modules.interfaces.UpdateInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
/*******************************************************************************
**
** Only the fields which exist in the record's values map will be updated.
** Note the difference between a field being in the value map, with a null value,
** vs. not being in the map. If the field (its key) is in the value map, with a
** null value, then the field will be updated to NULL. But if it's not in the
** map, then it'll be ignored. This would be to do a PATCH type operation, vs a
** PUT. See https://rapidapi.com/blog/put-vs-patch/
*******************************************************************************/
public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInterface
{
@ -48,52 +59,59 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
*******************************************************************************/
public UpdateResult execute(UpdateRequest updateRequest) throws QException
{
try
if(CollectionUtils.nullSafeIsEmpty(updateRequest.getRecords()))
{
throw (new QException("Request to update 0 records."));
}
UpdateResult rs = new UpdateResult();
QTableMetaData table = updateRequest.getTable();
List<QRecord> outputRecords = new ArrayList<>();
rs.setRecords(outputRecords);
// todo - sql batch for performance
// todo - if setting a bunch of records to have the same value, a single update where id IN?
Connection connection = getConnection(updateRequest);
int recordIndex = 0;
/////////////////////////////////////////////////////////////////////////////////////////////
// we want to do batch updates. But, since we only update the columns columns that //
// are present in each record, it means we may have different update SQL for each //
// record. So, we will first "hash" up the records by their list of fields being updated. //
/////////////////////////////////////////////////////////////////////////////////////////////
ListingHash<List<String>, QRecord> recordsByFieldBeingUpdated = new ListingHash<>();
for(QRecord record : updateRequest.getRecords())
{
List<QFieldMetaData> updateableFields = table.getFields().values().stream()
.filter(field -> !field.getName().equals("id")) // todo - intent here is to avoid non-updateable fields.
.filter(field -> record.getValues().containsKey(field.getName()))
List<String> updatableFields = table.getFields().values().stream()
.map(QFieldMetaData::getName)
// todo - intent here is to avoid non-updateable fields - but this
// should be like based on field.isUpdatable once that attribute exists
.filter(name -> !name.equals("id"))
.filter(name -> record.getValues().containsKey(name))
.toList();
recordsByFieldBeingUpdated.add(updatableFields, record);
String columns = updateableFields.stream()
.map(f -> this.getColumnName(f) + " = ?")
.collect(Collectors.joining(", "));
String tableName = getTableName(table);
StringBuilder sql = new StringBuilder("UPDATE ").append(tableName)
.append(" SET ").append(columns)
.append(" WHERE ").append(getColumnName(table.getField(table.getPrimaryKeyField()))).append(" = ?");
// todo sql customization - can edit sql and/or param list
//////////////////////////////////////////////////////////////////////////////
// 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 //
// note that if we want to capture updated values (like modify dates), then //
// we may want a map of primary key to output record, for easy updating. //
//////////////////////////////////////////////////////////////////////////////
QRecord outputRecord = new QRecord(record);
outputRecords.add(outputRecord);
try
{
List<Object> params = new ArrayList<>();
for(QFieldMetaData field : updateableFields)
{
Serializable value = record.getValue(field.getName());
value = scrubValue(field, value);
params.add(value);
}
params.add(record.getValue(table.getPrimaryKeyField()));
QueryManager.executeUpdate(connection, sql.toString(), params);
// todo - auto-updated values, e.g., modifyDate... maybe need to re-select?
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// todo - further optimization: if setting a bunch of records to have the same value, a single update where id IN ? //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
try(Connection connection = getConnection(updateRequest))
{
/////////////////////////////////////////////////////////////////////////////////////////////
// process each distinct list of fields being updated (e.g., each different SQL statement) //
/////////////////////////////////////////////////////////////////////////////////////////////
for(List<String> fieldsBeingUpdated : recordsByFieldBeingUpdated.keySet())
{
updateRecordsWithMatchingListOfFields(connection, table, recordsByFieldBeingUpdated.get(fieldsBeingUpdated), fieldsBeingUpdated);
}
return rs;
}
catch(Exception e)
{
@ -102,12 +120,133 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
}
}
return rs;
}
catch(Exception e)
/*******************************************************************************
**
*******************************************************************************/
private void updateRecordsWithMatchingListOfFields(Connection connection, QTableMetaData table, List<QRecord> recordList, List<String> fieldsBeingUpdated) throws SQLException
{
throw new QException("Error executing update: " + e.getMessage(), e);
////////////////////////////////////////////////////////////////////////////////
// check for an optimization - if all of the records have the same values for //
// all fields being updated, just do 1 update, with an IN list on the ids. //
////////////////////////////////////////////////////////////////////////////////
if(areAllValuesBeingUpdatedTheSame(recordList, fieldsBeingUpdated))
{
updateRecordsWithMatchingValuesAndFields(connection, table, recordList, fieldsBeingUpdated);
return;
}
String sql = writeUpdateSQLPrefix(table, fieldsBeingUpdated) + " = ?";
// todo sql customization? - let each table have custom sql and/or param list
////////////////////////////////////////////////////////
// build the list of list of values, from the records //
////////////////////////////////////////////////////////
List<List<Serializable>> values = new ArrayList<>();
for(QRecord record : recordList)
{
List<Serializable> rowValues = new ArrayList<>();
values.add(rowValues);
for(String fieldName : fieldsBeingUpdated)
{
Serializable value = record.getValue(fieldName);
value = scrubValue(table.getField(fieldName), value, false);
rowValues.add(value);
}
rowValues.add(record.getValue(table.getPrimaryKeyField()));
}
////////////////////////////////////////////////////////////////////////////////
// let query manager do the batch updates - note that it will internally page //
////////////////////////////////////////////////////////////////////////////////
QueryManager.executeBatchUpdate(connection, sql, values);
// todo - auto-updated values, e.g., modifyDate... maybe need to re-select?
}
/*******************************************************************************
**
*******************************************************************************/
private String writeUpdateSQLPrefix(QTableMetaData table, List<String> fieldsBeingUpdated)
{
String columns = fieldsBeingUpdated.stream()
.map(f -> this.getColumnName(table.getField(f)) + " = ?")
.collect(Collectors.joining(", "));
String tableName = getTableName(table);
return ("UPDATE " + tableName
+ " SET " + columns
+ " WHERE " + getColumnName(table.getField(table.getPrimaryKeyField())) + " ");
}
/*******************************************************************************
**
*******************************************************************************/
private void updateRecordsWithMatchingValuesAndFields(Connection connection, QTableMetaData table, List<QRecord> recordList, List<String> fieldsBeingUpdated) throws SQLException
{
String sql = writeUpdateSQLPrefix(table, fieldsBeingUpdated) + " IN (" + StringUtils.join(",", Collections.nCopies(recordList.size(), "?")) + ")";
// todo sql customization? - let each table have custom sql and/or param list
////////////////////////////////////////////////////////////////
// values in the update clause can come from the first record //
////////////////////////////////////////////////////////////////
QRecord record0 = recordList.get(0);
List<Object> params = new ArrayList<>();
for(String fieldName : fieldsBeingUpdated)
{
Serializable value = record0.getValue(fieldName);
value = scrubValue(table.getField(fieldName), value, false);
params.add(value);
}
//////////////////////////////////////////////////////////////////////
// values in the where clause (in list) are the id from each record //
//////////////////////////////////////////////////////////////////////
for(QRecord record : recordList)
{
params.add(record.getValue(table.getPrimaryKeyField()));
}
////////////////////////////////////////////////////////////////////////////////
// let query manager do the batch updates - note that it will internally page //
////////////////////////////////////////////////////////////////////////////////
QueryManager.executeUpdate(connection, sql, params);
}
/*******************************************************************************
**
*******************************************************************************/
private boolean areAllValuesBeingUpdatedTheSame(List<QRecord> recordList, List<String> fieldsBeingUpdated)
{
if(recordList.size() == 1)
{
return (true);
}
QRecord record0 = recordList.get(0);
for(int i = 1; i < recordList.size(); i++)
{
QRecord record = recordList.get(i);
for(String fieldName : fieldsBeingUpdated)
{
if(!Objects.equals(record0.getValue(fieldName), record.getValue(fieldName)))
{
return (false);
}
}
}
return (true);
}
}

View File

@ -57,7 +57,7 @@ public class ConnectionManager
}
case "h2":
{
jdbcURL = "jdbc:h2:" + backend.getHostName() + ":" + backend.getDatabaseName() + ";MODE=MySQL";
jdbcURL = "jdbc:h2:" + backend.getHostName() + ":" + backend.getDatabaseName() + ";MODE=MySQL;DB_CLOSE_DELAY=-1";
break;
}
default:

View File

@ -22,13 +22,12 @@
package com.kingsrook.qqq.backend.module.rdbms.jdbc;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
@ -43,13 +42,10 @@ import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
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.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.apache.commons.lang.NotImplementedException;
/*******************************************************************************
@ -114,6 +110,8 @@ public class QueryManager
*******************************************************************************/
public static void executeStatementForeachResult(Connection connection, String sql, ResultSetProcessor processor, Object... params) throws SQLException
{
throw (new NotImplementedException());
/*
PreparedStatement statement = null;
ResultSet resultSet = null;
@ -145,6 +143,7 @@ public class QueryManager
resultSet.close();
}
}
*/
}
@ -155,6 +154,8 @@ public class QueryManager
@SuppressWarnings("unchecked")
public static <T> T executeStatementForSingleValue(Connection connection, Class<T> returnClass, String sql, Object... params) throws SQLException
{
throw (new NotImplementedException());
/*
PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params);
statement.execute();
ResultSet resultSet = statement.getResultSet();
@ -203,6 +204,7 @@ public class QueryManager
{
return (null);
}
*/
}
@ -212,6 +214,8 @@ public class QueryManager
*******************************************************************************/
public static Map<String, Object> executeStatementForSingleRow(Connection connection, String sql, Object... params) throws SQLException
{
throw (new NotImplementedException());
/*
PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params);
statement.execute();
ResultSet resultSet = statement.getResultSet();
@ -231,6 +235,7 @@ public class QueryManager
{
return (null);
}
*/
}
@ -240,6 +245,8 @@ public class QueryManager
*******************************************************************************/
public static SimpleEntity executeStatementForSimpleEntity(Connection connection, String sql, Object... params) throws SQLException
{
throw (new NotImplementedException());
/*
PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params);
statement.execute();
ResultSet resultSet = statement.getResultSet();
@ -251,6 +258,7 @@ public class QueryManager
{
return (null);
}
*/
}
@ -260,6 +268,8 @@ public class QueryManager
*******************************************************************************/
public static List<Map<String, Object>> executeStatementForRows(Connection connection, String sql, Object... params) throws SQLException
{
throw (new NotImplementedException());
/*
List<Map<String, Object>> rs = new ArrayList<>();
PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params);
@ -278,6 +288,7 @@ public class QueryManager
}
return (rs);
*/
}
@ -287,6 +298,8 @@ public class QueryManager
*******************************************************************************/
public static List<SimpleEntity> executeStatementForSimpleEntityList(Connection connection, String sql, Object... params) throws SQLException
{
throw (new NotImplementedException());
/*
List<SimpleEntity> rs = new ArrayList<>();
PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params);
@ -300,6 +313,7 @@ public class QueryManager
}
return (rs);
*/
}
@ -309,6 +323,8 @@ public class QueryManager
*******************************************************************************/
public static SimpleEntity buildSimpleEntity(ResultSet resultSet) throws SQLException
{
throw (new NotImplementedException());
/*
SimpleEntity row = new SimpleEntity();
ResultSetMetaData metaData = resultSet.getMetaData();
@ -317,6 +333,7 @@ public class QueryManager
row.put(metaData.getColumnName(i), getObject(resultSet, i));
}
return row;
*/
}
@ -350,10 +367,13 @@ public class QueryManager
*******************************************************************************/
public static void executeUpdateVoid(Connection connection, String sql, Object... params) throws SQLException
{
throw (new NotImplementedException());
/*
try(PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params))
{
statement.executeUpdate();
}
*/
}
@ -363,10 +383,13 @@ public class QueryManager
*******************************************************************************/
public static void executeUpdateVoid(Connection connection, String sql, List<Object> params) throws SQLException
{
throw (new NotImplementedException());
/*
try(PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params))
{
statement.executeUpdate();
}
*/
}
@ -390,11 +413,14 @@ public class QueryManager
*******************************************************************************/
public static Integer executeUpdateForRowCount(Connection connection, String sql, List<Object> params) throws SQLException
{
throw (new NotImplementedException());
/*
try(PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params))
{
statement.executeUpdate();
return (statement.getUpdateCount());
}
*/
}
@ -404,6 +430,8 @@ public class QueryManager
*******************************************************************************/
public static Integer executeInsertForGeneratedId(Connection connection, String sql, Object... params) throws SQLException
{
throw (new NotImplementedException());
/*
try(PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS))
{
bindParams(params, statement);
@ -418,12 +446,13 @@ public class QueryManager
return (null);
}
}
*/
}
/*******************************************************************************
** todo - needs unit test
** todo - needs (specific) unit test
*******************************************************************************/
public static List<Integer> executeInsertForGeneratedIds(Connection connection, String sql, List<Object> params) throws SQLException
{
@ -448,6 +477,8 @@ public class QueryManager
*******************************************************************************/
public static void executeInsertForList(Connection connection, List<SimpleEntity> entityList) throws SQLException
{
throw (new NotImplementedException());
/*
List<List<SimpleEntity>> pages = CollectionUtils.getPages(entityList, PAGE_SIZE);
for(List<SimpleEntity> page : pages)
{
@ -474,6 +505,7 @@ public class QueryManager
page.clear();
}
pages.clear();
*/
}
@ -483,6 +515,8 @@ public class QueryManager
*******************************************************************************/
public static Integer executeInsert(Connection connection, SimpleEntity entity) throws SQLException
{
throw (new NotImplementedException());
/*
ArrayList<String> columns = new ArrayList<>(entity.keySet());
String sql = "INSERT INTO " + entity.getTableName() + "(" + StringUtils.join(",", columns) + ") VALUES (" + columns.stream().map(s -> "?").collect(Collectors.joining(",")) + ")";
@ -493,6 +527,32 @@ public class QueryManager
}
return (executeInsertForGeneratedId(connection, sql, params));
*/
}
/*******************************************************************************
**
*******************************************************************************/
public static void executeBatchUpdate(Connection connection, String updateSQL, List<List<Serializable>> values) throws SQLException
{
for(List<List<Serializable>> page : CollectionUtils.getPages(values, PAGE_SIZE))
{
PreparedStatement updatePS = connection.prepareStatement(updateSQL);
for(List<Serializable> row : page)
{
Object[] params = new Object[row.size()];
for(int i = 0; i < row.size(); i++)
{
params[i] = row.get(i);
}
bindParams(updatePS, params);
updatePS.addBatch();
}
updatePS.executeBatch();
}
}
@ -570,12 +630,13 @@ public class QueryManager
@SuppressWarnings("unchecked")
public static int bindParamObject(PreparedStatement statement, int index, Object value) throws SQLException
{
if(value instanceof TypeValuePair)
/* if(value instanceof TypeValuePair)
{
bindParamTypeValuePair(statement, index, (TypeValuePair<Object>) value);
return (1);
}
else if(value instanceof Integer)
else*/
if(value instanceof Integer)
{
bindParam(statement, index, (Integer) value);
return (1);
@ -675,16 +736,19 @@ public class QueryManager
/*******************************************************************************
**
*******************************************************************************/
/*
public static <T> TypeValuePair<T> param(Class<T> c, T v)
{
return (new TypeValuePair<>(c, v));
}
*/
/*******************************************************************************
**
*******************************************************************************/
/*
private static void bindParamTypeValuePair(PreparedStatement statement, int index, TypeValuePair<Object> value) throws SQLException
{
Object v = value.getValue();
@ -731,6 +795,7 @@ public class QueryManager
throw (new SQLException("Unexpected value type [" + t.getSimpleName() + "] in bindParamTypeValuePair."));
}
}
*/
@ -1044,12 +1109,15 @@ public class QueryManager
*******************************************************************************/
public static BigDecimal getBigDecimal(ResultSet resultSet, String column) throws SQLException
{
throw (new NotImplementedException());
/*
BigDecimal value = resultSet.getBigDecimal(column);
if(resultSet.wasNull())
{
return (null);
}
return (value);
*/
}
@ -1074,12 +1142,15 @@ public class QueryManager
*******************************************************************************/
public static Date getDate(ResultSet resultSet, String column) throws SQLException
{
throw (new NotImplementedException());
/*
Date value = resultSet.getDate(column);
if(resultSet.wasNull())
{
return (null);
}
return (value);
*/
}
@ -1104,6 +1175,8 @@ public class QueryManager
*******************************************************************************/
public static Calendar getCalendar(ResultSet resultSet, String column) throws SQLException
{
throw (new NotImplementedException());
/*
Timestamp value = resultSet.getTimestamp(column);
if(resultSet.wasNull())
{
@ -1112,6 +1185,7 @@ public class QueryManager
Calendar rs = Calendar.getInstance();
rs.setTimeInMillis(value.getTime());
return (rs);
*/
}
@ -1121,6 +1195,8 @@ public class QueryManager
*******************************************************************************/
public static Calendar getCalendar(ResultSet resultSet, int column) throws SQLException
{
throw (new NotImplementedException());
/*
Timestamp value = resultSet.getTimestamp(column);
if(resultSet.wasNull())
{
@ -1129,6 +1205,7 @@ public class QueryManager
Calendar rs = Calendar.getInstance();
rs.setTimeInMillis(value.getTime());
return (rs);
*/
}
@ -1139,6 +1216,8 @@ public class QueryManager
@SuppressWarnings("deprecation")
public static LocalDate getLocalDate(ResultSet resultSet, String column) throws SQLException
{
throw (new NotImplementedException());
/*
Timestamp value = resultSet.getTimestamp(column);
if(resultSet.wasNull())
{
@ -1147,6 +1226,7 @@ public class QueryManager
LocalDate date = LocalDate.of(value.getYear() + NINETEEN_HUNDRED, value.getMonth() + 1, value.getDate());
return (date);
*/
}
@ -1157,6 +1237,8 @@ public class QueryManager
@SuppressWarnings("deprecation")
public static LocalDateTime getLocalDateTime(ResultSet resultSet, String column) throws SQLException
{
throw (new NotImplementedException());
/*
Timestamp value = resultSet.getTimestamp(column);
if(resultSet.wasNull())
{
@ -1165,6 +1247,7 @@ public class QueryManager
LocalDateTime dateTime = LocalDateTime.of(value.getYear() + NINETEEN_HUNDRED, value.getMonth() + 1, value.getDate(), value.getHours(), value.getMinutes(), value.getSeconds(), 0);
return (dateTime);
*/
}
@ -1193,6 +1276,8 @@ public class QueryManager
@SuppressWarnings("deprecation")
public static OffsetDateTime getOffsetDateTime(ResultSet resultSet, String column) throws SQLException
{
throw (new NotImplementedException());
/*
Timestamp value = resultSet.getTimestamp(column);
if(resultSet.wasNull())
{
@ -1201,6 +1286,7 @@ public class QueryManager
OffsetDateTime dateTime = OffsetDateTime.of(value.getYear() + NINETEEN_HUNDRED, value.getMonth() + 1, value.getDate(), value.getHours(), value.getMinutes(), value.getSeconds(), 0, OffsetDateTime.now().getOffset());
return (dateTime);
*/
}
@ -1210,12 +1296,15 @@ public class QueryManager
*******************************************************************************/
public static Boolean getBoolean(ResultSet resultSet, String column) throws SQLException
{
throw (new NotImplementedException());
/*
Boolean value = resultSet.getBoolean(column);
if(resultSet.wasNull())
{
return (null);
}
return (value);
*/
}
@ -1225,12 +1314,15 @@ public class QueryManager
*******************************************************************************/
public static Boolean getBoolean(ResultSet resultSet, int column) throws SQLException
{
throw (new NotImplementedException());
/*
Boolean value = resultSet.getBoolean(column);
if(resultSet.wasNull())
{
return (null);
}
return (value);
*/
}
@ -1240,12 +1332,15 @@ public class QueryManager
*******************************************************************************/
public static Long getLong(ResultSet resultSet, int column) throws SQLException
{
throw (new NotImplementedException());
/*
long value = resultSet.getLong(column);
if(resultSet.wasNull())
{
return (null);
}
return (value);
*/
}
@ -1255,12 +1350,15 @@ public class QueryManager
*******************************************************************************/
public static Long getLong(ResultSet resultSet, String column) throws SQLException
{
throw (new NotImplementedException());
/*
long value = resultSet.getLong(column);
if(resultSet.wasNull())
{
return (null);
}
return (value);
*/
}
@ -1270,12 +1368,15 @@ public class QueryManager
*******************************************************************************/
public static Timestamp getTimestamp(ResultSet resultSet, int column) throws SQLException
{
throw (new NotImplementedException());
/*
Timestamp value = resultSet.getTimestamp(column);
if(resultSet.wasNull())
{
return (null);
}
return (value);
*/
}
@ -1285,12 +1386,15 @@ public class QueryManager
*******************************************************************************/
public static Timestamp getTimestamp(ResultSet resultSet, String column) throws SQLException
{
throw (new NotImplementedException());
/*
Timestamp value = resultSet.getTimestamp(column);
if(resultSet.wasNull())
{
return (null);
}
return (value);
*/
}
@ -1304,7 +1408,10 @@ public class QueryManager
*******************************************************************************/
public static Integer findIdForDaysAgo(Connection connection, String tableName, String dateFieldName, int goalDaysAgo) throws SQLException
{
throw (new NotImplementedException());
/*
return (findIdForTimeUnitAgo(connection, tableName, dateFieldName, goalDaysAgo, ChronoUnit.DAYS));
*/
}
@ -1314,8 +1421,11 @@ public class QueryManager
*******************************************************************************/
public static Integer findIdForTimestamp(Connection connection, String tableName, String dateFieldName, LocalDateTime timestamp) throws SQLException
{
throw (new NotImplementedException());
/*
long between = ChronoUnit.SECONDS.between(timestamp, LocalDateTime.now());
return (findIdForTimeUnitAgo(connection, tableName, dateFieldName, (int) between, ChronoUnit.SECONDS));
*/
}
@ -1325,6 +1435,8 @@ public class QueryManager
*******************************************************************************/
public static Integer findIdForTimeUnitAgo(Connection connection, String tableName, String dateFieldName, int goalUnitsAgo, ChronoUnit unit) throws SQLException
{
throw (new NotImplementedException());
/*
Integer maxId = executeStatementForSingleValue(connection, Integer.class, "SELECT MAX(id) FROM " + tableName);
Integer minId = executeStatementForSingleValue(connection, Integer.class, "SELECT MIN(id) FROM " + tableName);
@ -1340,6 +1452,7 @@ public class QueryManager
// Logger.logDebug("For [" + tableName + "], using min id [" + idForGoal + "], which is from [" + foundUnitsAgo + "] Units[" + unit + "] ago.");
return (idForGoal);
*/
}
@ -1349,6 +1462,8 @@ public class QueryManager
*******************************************************************************/
private static Integer findIdForTimeUnitAgo(Connection connection, String tableName, String dateFieldName, int goalUnitsAgo, Integer minId, Integer maxId, ChronoUnit unit) throws SQLException
{
throw (new NotImplementedException());
/*
Integer midId = minId + ((maxId - minId) / 2);
if(midId.equals(minId) || midId.equals(maxId))
{
@ -1368,6 +1483,7 @@ public class QueryManager
{
return (findIdForTimeUnitAgo(connection, tableName, dateFieldName, goalUnitsAgo, minId, midId, unit));
}
*/
}
@ -1377,6 +1493,8 @@ public class QueryManager
*******************************************************************************/
private static long getTimeUnitAgo(Connection connection, String tableName, String dateFieldName, Integer id, ChronoUnit unit) throws SQLException
{
throw (new NotImplementedException());
/*
LocalDateTime now = LocalDateTime.now();
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -1395,61 +1513,52 @@ public class QueryManager
// System.out.println("Unit[" + unit + "]'s ago: " + diff);
return diff;
}
*/
}
/*******************************************************************************
**
*******************************************************************************/
public static class TypeValuePair<T>
{
private Class<T> type;
private T value;
// public static class TypeValuePair<T>
// {
// private Class<T> type;
// private T value;
// /*******************************************************************************
// **
// *******************************************************************************/
// @SuppressWarnings("unchecked")
// public TypeValuePair(T value)
// {
// this.value = value;
// this.type = (Class<T>) value.getClass();
// }
// /*******************************************************************************
// **
// *******************************************************************************/
// public TypeValuePair(Class<T> type, T value)
// {
// this.type = type;
// this.value = value;
// }
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("unchecked")
public TypeValuePair(T value)
{
this.value = value;
this.type = (Class<T>) value.getClass();
}
// /*******************************************************************************
// **
// *******************************************************************************/
// public T getValue()
// {
// return (value);
// }
// /*******************************************************************************
// **
// *******************************************************************************/
// public Class<T> getType()
// {
// return (type);
// }
/*******************************************************************************
**
*******************************************************************************/
public TypeValuePair(Class<T> type, T value)
{
this.type = type;
this.value = value;
}
/*******************************************************************************
**
*******************************************************************************/
public T getValue()
{
return (value);
}
/*******************************************************************************
**
*******************************************************************************/
public Class<T> getType()
{
return (type);
}
}
// }
}

View File

@ -46,7 +46,8 @@ public class RDBMSActionTest
protected void primeTestDatabase() throws Exception
{
ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.getConnection(TestUtils.defineBackend());
try(Connection connection = connectionManager.getConnection(TestUtils.defineBackend()))
{
InputStream primeTestDatabaseSqlStream = RDBMSActionTest.class.getResourceAsStream("/prime-test-database.sql");
assertNotNull(primeTestDatabaseSqlStream);
List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream);
@ -57,6 +58,7 @@ public class RDBMSActionTest
QueryManager.executeUpdate(connection, sql);
}
}
}

View File

@ -22,7 +22,10 @@
package com.kingsrook.qqq.backend.module.rdbms.actions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -31,6 +34,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
@ -51,6 +55,38 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testUpdateNullList()
{
UpdateRequest updateRequest = initUpdateRequest();
updateRequest.setRecords(null);
assertThrows(QException.class, () ->
{
new RDBMSUpdateAction().execute(updateRequest);
});
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testUpdateEmptyList()
{
UpdateRequest updateRequest = initUpdateRequest();
updateRequest.setRecords(Collections.emptyList());
assertThrows(QException.class, () ->
{
new RDBMSUpdateAction().execute(updateRequest);
});
}
/*******************************************************************************
**
*******************************************************************************/
@ -94,7 +130,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
**
*******************************************************************************/
@Test
public void testUpdateMany() throws Exception
public void testUpdateManyWithDifferentColumnsAndValues() throws Exception
{
UpdateRequest updateRequest = initUpdateRequest();
QRecord record1 = new QRecord().withTableName("person")
@ -108,11 +144,17 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
.withValue("firstName", "Wilt")
.withValue("birthDate", null);
updateRequest.setRecords(List.of(record1, record2));
QRecord record3 = new QRecord().withTableName("person")
.withValue("id", 5)
.withValue("firstName", "Richard")
.withValue("birthDate", null);
updateRequest.setRecords(List.of(record1, record2, record3));
UpdateResult updateResult = new RDBMSUpdateAction().execute(updateRequest);
assertEquals(2, updateResult.getRecords().size(), "Should return 2 rows");
assertEquals(3, updateResult.getRecords().size(), "Should return 3 rows");
assertEquals(1, updateResult.getRecords().get(0).getValue("id"), "Should have expected ids in the row");
assertEquals(3, updateResult.getRecords().get(1).getValue("id"), "Should have expected ids in the row");
assertEquals(5, updateResult.getRecords().get(2).getValue("id"), "Should have expected ids in the row");
// todo - add errors to QRecord? assertTrue(updateResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors");
runTestSql("SELECT * FROM person WHERE last_name = 'From Bewitched'", (rs -> {
int rowsFound = 0;
@ -137,6 +179,100 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
}
assertEquals(1, rowsFound);
}));
runTestSql("SELECT * FROM person WHERE last_name = 'Richardson'", (rs -> {
int rowsFound = 0;
while(rs.next())
{
rowsFound++;
assertEquals(5, rs.getInt("id"));
assertEquals("Richard", rs.getString("first_name"));
assertNull(rs.getString("birth_date"));
}
assertEquals(1, rowsFound);
}));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testUpdateManyWithSameColumnsDifferentValues() throws Exception
{
UpdateRequest updateRequest = initUpdateRequest();
QRecord record1 = new QRecord().withTableName("person")
.withValue("id", 1)
.withValue("firstName", "Darren")
.withValue("lastName", "From Bewitched")
.withValue("birthDate", "1900-01-01");
QRecord record2 = new QRecord().withTableName("person")
.withValue("id", 3)
.withValue("firstName", "Wilt")
.withValue("lastName", "Tim's Uncle")
.withValue("birthDate", null);
updateRequest.setRecords(List.of(record1, record2));
UpdateResult updateResult = new RDBMSUpdateAction().execute(updateRequest);
assertEquals(2, updateResult.getRecords().size(), "Should return 2 rows");
assertEquals(1, updateResult.getRecords().get(0).getValue("id"), "Should have expected ids in the row");
assertEquals(3, updateResult.getRecords().get(1).getValue("id"), "Should have expected ids in the row");
// todo - add errors to QRecord? assertTrue(updateResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors");
runTestSql("SELECT * FROM person WHERE last_name = 'From Bewitched'", (rs -> {
int rowsFound = 0;
while(rs.next())
{
rowsFound++;
assertEquals(1, rs.getInt("id"));
assertEquals("Darren", rs.getString("first_name"));
assertEquals("From Bewitched", rs.getString("last_name"));
assertEquals("1900-01-01", rs.getString("birth_date"));
}
assertEquals(1, rowsFound);
}));
runTestSql("SELECT * FROM person WHERE last_name = 'Tim''s Uncle'", (rs -> {
int rowsFound = 0;
while(rs.next())
{
rowsFound++;
assertEquals(3, rs.getInt("id"));
assertEquals("Wilt", rs.getString("first_name"));
assertEquals("Tim's Uncle", rs.getString("last_name"));
assertNull(rs.getString("birth_date"));
}
assertEquals(1, rowsFound);
}));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testUpdateManyWithSameColumnsSameValues() throws Exception
{
UpdateRequest updateRequest = initUpdateRequest();
List<QRecord> records = new ArrayList<>();
for(int i = 1; i <= 5; i++)
{
records.add(new QRecord().withTableName("person").withValue("id", i).withValue("birthDate", "1999-09-09"));
}
updateRequest.setRecords(records);
UpdateResult updateResult = new RDBMSUpdateAction().execute(updateRequest);
assertEquals(5, updateResult.getRecords().size(), "Should return 5 rows");
// todo - add errors to QRecord? assertTrue(updateResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors");
runTestSql("SELECT * FROM person WHERE id <= 5", (rs -> {
int rowsFound = 0;
while(rs.next())
{
rowsFound++;
assertEquals("1999-09-09", rs.getString("birth_date"));
}
assertEquals(5, rowsFound);
}));
}

View File

@ -0,0 +1,34 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.module.rdbms.jdbc;
import static org.junit.jupiter.api.Assertions.*;
/*******************************************************************************
**
*******************************************************************************/
class QueryManagerTest
{
}