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 executor: java17
steps: steps:
- run_maven: - run_maven:
maven_subcommand: test maven_subcommand: verify
- slack/notify: - slack/notify:
event: fail 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 # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid* hs_err_pid*
.DS_Store

View File

@ -181,8 +181,8 @@
</module> </module>
--> -->
<module name="OverloadMethodsDeclarationOrder"/> <module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<!-- <!--
<module name="VariableDeclarationUsageDistance"/>
<module name="CustomImportOrder"> <module name="CustomImportOrder">
<property name="sortImportsInGroupAlphabetically" value="true"/> <property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" 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. ** 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)) if("".equals(value))
{ {
@ -111,9 +111,12 @@ public abstract class AbstractRDBMSAction
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
// todo - let this come from something in the field // // todo - let this come from something in the field //
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
if(value == null && (field.getName().equals("createDate") || field.getName().equals("modifyDate"))) if(value == null)
{ {
value = OffsetDateTime.now(); if((isInsert && field.getName().equals("createDate")) || field.getName().equals("modifyDate"))
{
value = OffsetDateTime.now();
}
} }
return (value); return (value);

View File

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

View File

@ -24,8 +24,11 @@ package com.kingsrook.qqq.backend.module.rdbms.actions;
import java.io.Serializable; import java.io.Serializable;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.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.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
import com.kingsrook.qqq.backend.core.modules.interfaces.UpdateInterface; 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; 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 public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInterface
{ {
@ -48,66 +59,194 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
*******************************************************************************/ *******************************************************************************/
public UpdateResult execute(UpdateRequest updateRequest) throws QException public UpdateResult execute(UpdateRequest updateRequest) throws QException
{ {
try if(CollectionUtils.nullSafeIsEmpty(updateRequest.getRecords()))
{ {
UpdateResult rs = new UpdateResult(); throw (new QException("Request to update 0 records."));
QTableMetaData table = updateRequest.getTable(); }
List<QRecord> outputRecords = new ArrayList<>(); UpdateResult rs = new UpdateResult();
rs.setRecords(outputRecords); QTableMetaData table = updateRequest.getTable();
// todo - sql batch for performance List<QRecord> outputRecords = new ArrayList<>();
// todo - if setting a bunch of records to have the same value, a single update where id IN? rs.setRecords(outputRecords);
Connection connection = getConnection(updateRequest);
int recordIndex = 0; /////////////////////////////////////////////////////////////////////////////////////////////
for(QRecord record : updateRequest.getRecords()) // 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<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);
//////////////////////////////////////////////////////////////////////////////
// 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);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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())
{ {
List<QFieldMetaData> updateableFields = table.getFields().values().stream() updateRecordsWithMatchingListOfFields(connection, table, recordsByFieldBeingUpdated.get(fieldsBeingUpdated), fieldsBeingUpdated);
.filter(field -> !field.getName().equals("id")) // todo - intent here is to avoid non-updateable fields.
.filter(field -> record.getValues().containsKey(field.getName()))
.toList();
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
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?
}
catch(Exception e)
{
// todo - how to communicate errors??? outputRecord.setErrors(new ArrayList<>(List.of(e)));
throw new QException("Error executing update: " + e.getMessage(), e);
}
} }
return rs; return rs;
} }
catch(Exception e) catch(Exception e)
{ {
// todo - how to communicate errors??? outputRecord.setErrors(new ArrayList<>(List.of(e)));
throw new QException("Error executing update: " + e.getMessage(), e); throw new QException("Error executing update: " + e.getMessage(), e);
} }
} }
/*******************************************************************************
**
*******************************************************************************/
private void updateRecordsWithMatchingListOfFields(Connection connection, QTableMetaData table, List<QRecord> recordList, List<String> fieldsBeingUpdated) throws SQLException
{
////////////////////////////////////////////////////////////////////////////////
// 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": 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; break;
} }
default: default:

View File

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

@ -45,16 +45,18 @@ public class RDBMSActionTest
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected void primeTestDatabase() throws Exception protected void primeTestDatabase() throws Exception
{ {
ConnectionManager connectionManager = new ConnectionManager(); 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);
lines = lines.stream().filter(line -> !line.startsWith("-- ")).toList();
String joinedSQL = String.join("\n", lines);
for(String sql : joinedSQL.split(";"))
{ {
QueryManager.executeUpdate(connection, sql); InputStream primeTestDatabaseSqlStream = RDBMSActionTest.class.getResourceAsStream("/prime-test-database.sql");
assertNotNull(primeTestDatabaseSqlStream);
List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream);
lines = lines.stream().filter(line -> !line.startsWith("-- ")).toList();
String joinedSQL = String.join("\n", lines);
for(String sql : joinedSQL.split(";"))
{
QueryManager.executeUpdate(connection, sql);
}
} }
} }

View File

@ -22,7 +22,10 @@
package com.kingsrook.qqq.backend.module.rdbms.actions; package com.kingsrook.qqq.backend.module.rdbms.actions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; 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.UpdateRequest;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult; import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult;
import com.kingsrook.qqq.backend.core.model.data.QRecord; 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 org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail; 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 @Test
public void testUpdateMany() throws Exception public void testUpdateManyWithDifferentColumnsAndValues() throws Exception
{ {
UpdateRequest updateRequest = initUpdateRequest(); UpdateRequest updateRequest = initUpdateRequest();
QRecord record1 = new QRecord().withTableName("person") QRecord record1 = new QRecord().withTableName("person")
@ -108,11 +144,17 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
.withValue("firstName", "Wilt") .withValue("firstName", "Wilt")
.withValue("birthDate", null); .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); 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(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(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"); // 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 -> { runTestSql("SELECT * FROM person WHERE last_name = 'From Bewitched'", (rs -> {
int rowsFound = 0; int rowsFound = 0;
@ -137,6 +179,100 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
} }
assertEquals(1, rowsFound); 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
{
}