Merge branch 'release/0.1.0'

This commit is contained in:
2022-07-14 10:03:58 -05:00
22 changed files with 1320 additions and 384 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

@ -25,7 +25,7 @@
<groupId>com.kingsrook.qqq</groupId> <groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-module-rdbms</artifactId> <artifactId>qqq-backend-module-rdbms</artifactId>
<version>0.0.0</version> <version>0.1.0</version>
<scm> <scm>
<connection>scm:git:git@github.com:Kingsrook/qqq-backend-module-rdbms.git</connection> <connection>scm:git:git@github.com:Kingsrook/qqq-backend-module-rdbms.git</connection>
@ -51,7 +51,7 @@
<dependency> <dependency>
<groupId>com.kingsrook.qqq</groupId> <groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-core</artifactId> <artifactId>qqq-backend-core</artifactId>
<version>0.0.0</version> <version>0.1.0</version>
</dependency> </dependency>
<!-- 3rd party deps specifically for this module --> <!-- 3rd party deps specifically for this module -->
@ -67,6 +67,7 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- Common deps for all qqq modules --> <!-- Common deps for all qqq modules -->
<dependency> <dependency>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>

View File

@ -24,11 +24,13 @@ package com.kingsrook.qqq.backend.module.rdbms;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QTableBackendDetails; import com.kingsrook.qqq.backend.core.model.metadata.QTableBackendDetails;
import com.kingsrook.qqq.backend.core.modules.interfaces.CountInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.DeleteInterface; import com.kingsrook.qqq.backend.core.modules.interfaces.DeleteInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.InsertInterface; import com.kingsrook.qqq.backend.core.modules.interfaces.InsertInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface; import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.QueryInterface; import com.kingsrook.qqq.backend.core.modules.interfaces.QueryInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.UpdateInterface; import com.kingsrook.qqq.backend.core.modules.interfaces.UpdateInterface;
import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSCountAction;
import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSDeleteAction; import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSDeleteAction;
import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSInsertAction; import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSInsertAction;
import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSQueryAction; import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSQueryAction;
@ -74,6 +76,18 @@ public class RDBMSBackendModule implements QBackendModuleInterface
/*******************************************************************************
**
*******************************************************************************/
@Override
public CountInterface getCountInterface()
{
return (new RDBMSCountAction());
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -25,8 +25,13 @@ 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.sql.SQLException;
import java.time.OffsetDateTime; import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.model.actions.AbstractQTableRequest; import com.kingsrook.qqq.backend.core.model.actions.AbstractQTableRequest;
import com.kingsrook.qqq.backend.core.model.actions.query.QFilterCriteria;
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.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
@ -92,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))
{ {
@ -103,14 +108,201 @@ public abstract class AbstractRDBMSAction
} }
} }
//////////////////////////////////////////////////////
// todo - let this come from something in the field //
//////////////////////////////////////////////////////
if(value == null && (field.getName().equals("createDate") || field.getName().equals("modifyDate")))
{
value = OffsetDateTime.now();
}
return (value); return (value);
} }
/*******************************************************************************
** If the table has a field with the given name, then set the given value in the
** given record.
*******************************************************************************/
protected void setValueIfTableHasField(QRecord record, QTableMetaData table, String fieldName, Serializable value)
{
QFieldMetaData field = table.getField(fieldName);
if(field != null)
{
record.setValue(fieldName, value);
}
}
/*******************************************************************************
**
*******************************************************************************/
protected String makeWhereClause(QTableMetaData table, List<QFilterCriteria> criteria, List<Serializable> params) throws IllegalArgumentException
{
List<String> clauses = new ArrayList<>();
for(QFilterCriteria criterion : criteria)
{
QFieldMetaData field = table.getField(criterion.getFieldName());
List<Serializable> values = criterion.getValues() == null ? new ArrayList<>() : new ArrayList<>(criterion.getValues());
String column = getColumnName(field);
String clause = column;
Integer expectedNoOfParams = null;
switch(criterion.getOperator())
{
case EQUALS:
{
clause += " = ? ";
expectedNoOfParams = 1;
break;
}
case NOT_EQUALS:
{
clause += " != ? ";
expectedNoOfParams = 1;
break;
}
case IN:
{
clause += " IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ") ";
break;
}
case NOT_IN:
{
clause += " NOT IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ") ";
break;
}
case STARTS_WITH:
{
clause += " LIKE ? ";
editFirstValue(values, (s -> s + "%"));
expectedNoOfParams = 1;
break;
}
case ENDS_WITH:
{
clause += " LIKE ? ";
editFirstValue(values, (s -> "%" + s));
expectedNoOfParams = 1;
break;
}
case CONTAINS:
{
clause += " LIKE ? ";
editFirstValue(values, (s -> "%" + s + "%"));
expectedNoOfParams = 1;
break;
}
case NOT_STARTS_WITH:
{
clause += " NOT LIKE ? ";
editFirstValue(values, (s -> s + "%"));
expectedNoOfParams = 1;
break;
}
case NOT_ENDS_WITH:
{
clause += " NOT LIKE ? ";
editFirstValue(values, (s -> "%" + s));
expectedNoOfParams = 1;
break;
}
case NOT_CONTAINS:
{
clause += " NOT LIKE ? ";
editFirstValue(values, (s -> "%" + s + "%"));
expectedNoOfParams = 1;
break;
}
case LESS_THAN:
{
clause += " < ? ";
expectedNoOfParams = 1;
break;
}
case LESS_THAN_OR_EQUALS:
{
clause += " <= ? ";
expectedNoOfParams = 1;
break;
}
case GREATER_THAN:
{
clause += " > ? ";
expectedNoOfParams = 1;
break;
}
case GREATER_THAN_OR_EQUALS:
{
clause += " >= ? ";
expectedNoOfParams = 1;
break;
}
case IS_BLANK:
{
clause += " IS NULL ";
if(isString(field.getType()))
{
clause += " OR " + column + " = '' ";
}
expectedNoOfParams = 0;
break;
}
case IS_NOT_BLANK:
{
clause += " IS NOT NULL ";
if(isString(field.getType()))
{
clause += " AND " + column + " !+ '' ";
}
expectedNoOfParams = 0;
break;
}
case BETWEEN:
{
clause += " BETWEEN ? AND ? ";
expectedNoOfParams = 2;
break;
}
case NOT_BETWEEN:
{
clause += " NOT BETWEEN ? AND ? ";
expectedNoOfParams = 2;
break;
}
default:
{
throw new IllegalArgumentException("Unexpected operator: " + criterion.getOperator());
}
}
clauses.add("(" + clause + ")");
if(expectedNoOfParams != null)
{
if(!expectedNoOfParams.equals(values.size()))
{
throw new IllegalArgumentException("Incorrect number of values given for criteria [" + field.getName() + "]");
}
}
params.addAll(values);
}
return (String.join(" AND ", clauses));
}
/*******************************************************************************
**
*******************************************************************************/
private static void editFirstValue(List<Serializable> values, Function<String, String> editFunction)
{
if(values.size() > 0)
{
values.set(0, editFunction.apply(String.valueOf(values.get(0))));
}
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean isString(QFieldType fieldType)
{
return fieldType == QFieldType.STRING || fieldType == QFieldType.TEXT || fieldType == QFieldType.HTML || fieldType == QFieldType.PASSWORD;
}
} }

View File

@ -0,0 +1,97 @@
/*
* 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.actions;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.count.CountRequest;
import com.kingsrook.qqq.backend.core.model.actions.count.CountResult;
import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
import com.kingsrook.qqq.backend.core.modules.interfaces.CountInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
**
*******************************************************************************/
public class RDBMSCountAction extends AbstractRDBMSAction implements CountInterface
{
private static final Logger LOG = LogManager.getLogger(RDBMSCountAction.class);
/*******************************************************************************
**
*******************************************************************************/
public CountResult execute(CountRequest countRequest) throws QException
{
try
{
QTableMetaData table = countRequest.getTable();
String tableName = getTableName(table);
String sql = "SELECT count(*) as record_count FROM " + tableName;
QQueryFilter filter = countRequest.getFilter();
List<Serializable> params = new ArrayList<>();
if(filter != null && CollectionUtils.nullSafeHasContents(filter.getCriteria()))
{
sql += " WHERE " + makeWhereClause(table, filter.getCriteria(), params);
}
// todo sql customization - can edit sql and/or param list
CountResult rs = new CountResult();
try(Connection connection = getConnection(countRequest))
{
QueryManager.executeStatement(connection, sql, ((ResultSet resultSet) ->
{
ResultSetMetaData metaData = resultSet.getMetaData();
if(resultSet.next())
{
rs.setCount(resultSet.getInt("record_count"));
}
}), params);
}
return rs;
}
catch(Exception e)
{
LOG.warn("Error executing count", e);
throw new QException("Error executing count", e);
}
}
}

View File

@ -65,16 +65,18 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte
// todo sql customization - can edit sql and/or param list // todo sql customization - can edit sql and/or param list
Connection connection = getConnection(deleteRequest); try(Connection connection = getConnection(deleteRequest))
QueryManager.executeUpdateForRowCount(connection, sql, params);
List<QRecord> outputRecords = new ArrayList<>();
rs.setRecords(outputRecords);
for(Serializable primaryKey : deleteRequest.getPrimaryKeys())
{ {
QRecord qRecord = new QRecord().withTableName(deleteRequest.getTableName()).withValue("id", primaryKey); QueryManager.executeUpdateForRowCount(connection, sql, params);
// todo uh, identify any errors? List<QRecord> outputRecords = new ArrayList<>();
QRecord outputRecord = new QRecord(qRecord); rs.setRecords(outputRecords);
outputRecords.add(outputRecord); for(Serializable primaryKey : deleteRequest.getPrimaryKeys())
{
QRecord qRecord = new QRecord().withTableName(deleteRequest.getTableName()).withValue("id", primaryKey);
// todo uh, identify any errors?
QRecord outputRecord = new QRecord(qRecord);
outputRecords.add(outputRecord);
}
} }
return rs; return rs;

View File

@ -24,6 +24,7 @@ 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.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -36,6 +37,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
import com.kingsrook.qqq.backend.core.modules.interfaces.InsertInterface; import com.kingsrook.qqq.backend.core.modules.interfaces.InsertInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/******************************************************************************* /*******************************************************************************
@ -43,22 +46,38 @@ import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
*******************************************************************************/ *******************************************************************************/
public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInterface public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInterface
{ {
private static final Logger LOG = LogManager.getLogger(RDBMSInsertAction.class);
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public InsertResult execute(InsertRequest insertRequest) throws QException public InsertResult execute(InsertRequest insertRequest) throws QException
{ {
InsertResult rs = new InsertResult();
if(CollectionUtils.nullSafeIsEmpty(insertRequest.getRecords())) if(CollectionUtils.nullSafeIsEmpty(insertRequest.getRecords()))
{ {
throw (new QException("Request to insert 0 records.")); LOG.info("Insert request called with 0 records. Returning with no-op");
rs.setRecords(new ArrayList<>());
return (rs);
}
QTableMetaData table = insertRequest.getTable();
Instant now = Instant.now();
for(QRecord record : insertRequest.getRecords())
{
///////////////////////////////////////////
// todo .. better (not hard-coded names) //
///////////////////////////////////////////
setValueIfTableHasField(record, table, "createDate", now);
setValueIfTableHasField(record, table, "modifyDate", now);
} }
try try
{ {
InsertResult rs = new InsertResult();
QTableMetaData table = insertRequest.getTable();
List<QFieldMetaData> insertableFields = table.getFields().values().stream() List<QFieldMetaData> insertableFields = table.getFields().values().stream()
.filter(field -> !field.getName().equals("id")) // todo - intent here is to avoid non-insertable fields. .filter(field -> !field.getName().equals("id")) // todo - intent here is to avoid non-insertable fields.
.toList(); .toList();
@ -74,42 +93,41 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte
StringBuilder sql = new StringBuilder("INSERT INTO ").append(tableName).append("(").append(columns).append(") VALUES"); StringBuilder sql = new StringBuilder("INSERT INTO ").append(tableName).append("(").append(columns).append(") VALUES");
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
int recordIndex = 0; try(Connection connection = getConnection(insertRequest))
for(QRecord record : insertRequest.getRecords())
{ {
if(recordIndex++ > 0) for(List<QRecord> page : CollectionUtils.getPages(insertRequest.getRecords(), QueryManager.PAGE_SIZE))
{ {
sql.append(","); int recordIndex = 0;
for(QRecord record : page)
{
if(recordIndex++ > 0)
{
sql.append(",");
}
sql.append("(").append(questionMarks).append(")");
for(QFieldMetaData field : insertableFields)
{
Serializable value = record.getValue(field.getName());
value = scrubValue(field, value, true);
params.add(value);
}
}
// todo sql customization - can edit sql and/or param list
// todo - non-serial-id style tables
// todo - other generated values, e.g., createDate... maybe need to re-select?
List<Integer> idList = QueryManager.executeInsertForGeneratedIds(connection, sql.toString(), params);
List<QRecord> outputRecords = new ArrayList<>();
rs.setRecords(outputRecords);
int index = 0;
for(QRecord record : insertRequest.getRecords())
{
Integer id = idList.get(index++);
QRecord outputRecord = new QRecord(record);
outputRecord.setValue(table.getPrimaryKeyField(), id);
outputRecords.add(outputRecord);
}
} }
sql.append("(").append(questionMarks).append(")");
for(QFieldMetaData field : insertableFields)
{
Serializable value = record.getValue(field.getName());
value = scrubValue(field, value);
params.add(value);
}
}
// todo sql customization - can edit sql and/or param list
// QueryResult rs = new QueryResult();
// List<QRecord> records = new ArrayList<>();
// rs.setRecords(records);
// todo - non-serial-id style tables
// todo - other generated values, e.g., createDate... maybe need to re-select?
Connection connection = getConnection(insertRequest);
List<Integer> idList = QueryManager.executeInsertForGeneratedIds(connection, sql.toString(), params);
List<QRecord> outputRecords = new ArrayList<>();
rs.setRecords(outputRecords);
int index = 0;
for(QRecord record : insertRequest.getRecords())
{
Integer id = idList.get(index++);
QRecord outputRecord = new QRecord(record);
outputRecord.setValue(table.getPrimaryKeyField(), id);
outputRecords.add(outputRecord);
} }
return rs; return rs;

View File

@ -56,6 +56,8 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
{ {
private static final Logger LOG = LogManager.getLogger(RDBMSQueryAction.class); private static final Logger LOG = LogManager.getLogger(RDBMSQueryAction.class);
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -102,29 +104,31 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
List<QRecord> records = new ArrayList<>(); List<QRecord> records = new ArrayList<>();
rs.setRecords(records); rs.setRecords(records);
Connection connection = getConnection(queryRequest); try(Connection connection = getConnection(queryRequest))
QueryManager.executeStatement(connection, sql, ((ResultSet resultSet) ->
{ {
ResultSetMetaData metaData = resultSet.getMetaData(); QueryManager.executeStatement(connection, sql, ((ResultSet resultSet) ->
while(resultSet.next())
{ {
// todo - should refactor this for view etc to use too. ResultSetMetaData metaData = resultSet.getMetaData();
// todo - Add display values (String labels for possibleValues, formatted #'s, etc) while(resultSet.next())
QRecord record = new QRecord();
records.add(record);
record.setTableName(table.getName());
LinkedHashMap<String, Serializable> values = new LinkedHashMap<>();
record.setValues(values);
for(int i = 1; i <= metaData.getColumnCount(); i++)
{ {
QFieldMetaData qFieldMetaData = fieldList.get(i - 1); // todo - should refactor this for view etc to use too.
Serializable value = getValue(qFieldMetaData, resultSet, i); // todo - Add display values (String labels for possibleValues, formatted #'s, etc)
values.put(qFieldMetaData.getName(), value); QRecord record = new QRecord();
} records.add(record);
} record.setTableName(table.getName());
LinkedHashMap<String, Serializable> values = new LinkedHashMap<>();
record.setValues(values);
}), params); for(int i = 1; i <= metaData.getColumnCount(); i++)
{
QFieldMetaData qFieldMetaData = fieldList.get(i - 1);
Serializable value = getValue(qFieldMetaData, resultSet, i);
values.put(qFieldMetaData.getName(), value);
}
}
}), params);
}
return rs; return rs;
} }
@ -177,186 +181,6 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
/*******************************************************************************
**
*******************************************************************************/
private String makeWhereClause(QTableMetaData table, List<QFilterCriteria> criteria, List<Serializable> params) throws IllegalArgumentException
{
List<String> clauses = new ArrayList<>();
for(QFilterCriteria criterion : criteria)
{
QFieldMetaData field = table.getField(criterion.getFieldName());
List<Serializable> values = criterion.getValues() == null ? new ArrayList<>() : new ArrayList<>(criterion.getValues());
String column = getColumnName(field);
String clause = column;
Integer expectedNoOfParams = null;
switch(criterion.getOperator())
{
case EQUALS:
{
clause += " = ? ";
expectedNoOfParams = 1;
break;
}
case NOT_EQUALS:
{
clause += " != ? ";
expectedNoOfParams = 1;
break;
}
case IN:
{
clause += " IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ") ";
break;
}
case NOT_IN:
{
clause += " NOT IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ") ";
break;
}
case STARTS_WITH:
{
clause += " LIKE ? ";
editFirstValue(values, (s -> s + "%"));
expectedNoOfParams = 1;
break;
}
case ENDS_WITH:
{
clause += " LIKE ? ";
editFirstValue(values, (s -> "%" + s));
expectedNoOfParams = 1;
break;
}
case CONTAINS:
{
clause += " LIKE ? ";
editFirstValue(values, (s -> "%" + s + "%"));
expectedNoOfParams = 1;
break;
}
case NOT_STARTS_WITH:
{
clause += " NOT LIKE ? ";
editFirstValue(values, (s -> s + "%"));
expectedNoOfParams = 1;
break;
}
case NOT_ENDS_WITH:
{
clause += " NOT LIKE ? ";
editFirstValue(values, (s -> "%" + s));
expectedNoOfParams = 1;
break;
}
case NOT_CONTAINS:
{
clause += " NOT LIKE ? ";
editFirstValue(values, (s -> "%" + s + "%"));
expectedNoOfParams = 1;
break;
}
case LESS_THAN:
{
clause += " < ? ";
expectedNoOfParams = 1;
break;
}
case LESS_THAN_OR_EQUALS:
{
clause += " <= ? ";
expectedNoOfParams = 1;
break;
}
case GREATER_THAN:
{
clause += " > ? ";
expectedNoOfParams = 1;
break;
}
case GREATER_THAN_OR_EQUALS:
{
clause += " >= ? ";
expectedNoOfParams = 1;
break;
}
case IS_BLANK:
{
clause += " IS NULL ";
if(isString(field.getType()))
{
clause += " OR " + column + " = '' ";
}
expectedNoOfParams = 0;
break;
}
case IS_NOT_BLANK:
{
clause += " IS NOT NULL ";
if(isString(field.getType()))
{
clause += " AND " + column + " !+ '' ";
}
expectedNoOfParams = 0;
break;
}
case BETWEEN:
{
clause += " BETWEEN ? AND ? ";
expectedNoOfParams = 2;
break;
}
case NOT_BETWEEN:
{
clause += " NOT BETWEEN ? AND ? ";
expectedNoOfParams = 2;
break;
}
default:
{
throw new IllegalArgumentException("Unexpected operator: " + criterion.getOperator());
}
}
clauses.add("(" + clause + ")");
if(expectedNoOfParams != null)
{
if(!expectedNoOfParams.equals(values.size()))
{
throw new IllegalArgumentException("Incorrect number of values given for criteria [" + field.getName() + "]");
}
}
params.addAll(values);
}
return (String.join(" AND ", clauses));
}
/*******************************************************************************
**
*******************************************************************************/
private void editFirstValue(List<Serializable> values, Function<String, String> editFunction)
{
if(values.size() > 0)
{
values.set(0, editFunction.apply(String.valueOf(values.get(0))));
}
}
/*******************************************************************************
**
*******************************************************************************/
private boolean isString(QFieldType fieldType)
{
return fieldType == QFieldType.STRING || fieldType == QFieldType.TEXT || fieldType == QFieldType.HTML || fieldType == QFieldType.PASSWORD;
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -24,8 +24,12 @@ 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.time.Instant;
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,80 +38,227 @@ 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;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/******************************************************************************* /*******************************************************************************
** ** 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
{ {
private static final Logger LOG = LogManager.getLogger(RDBMSUpdateAction.class);
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public UpdateResult execute(UpdateRequest updateRequest) throws QException public UpdateResult execute(UpdateRequest updateRequest) throws QException
{ {
try UpdateResult rs = new UpdateResult();
if(CollectionUtils.nullSafeIsEmpty(updateRequest.getRecords()))
{ {
UpdateResult rs = new UpdateResult(); LOG.info("Update request called with 0 records. Returning with no-op");
QTableMetaData table = updateRequest.getTable(); rs.setRecords(new ArrayList<>());
return (rs);
}
List<QRecord> outputRecords = new ArrayList<>(); QTableMetaData table = updateRequest.getTable();
rs.setRecords(outputRecords); Instant now = Instant.now();
// 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())
{
////////////////////////////////////////////
// todo .. better (not a hard-coded name) //
////////////////////////////////////////////
setValueIfTableHasField(record, table, "modifyDate", now);
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);
}
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);
}
/*******************************************************************************
**
*******************************************************************************/
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
{
for(List<QRecord> page : CollectionUtils.getPages(recordList, QueryManager.PAGE_SIZE))
{
String sql = writeUpdateSQLPrefix(table, fieldsBeingUpdated) + " IN (" + StringUtils.join(",", Collections.nCopies(page.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 = page.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 : page)
{
params.add(record.getValue(table.getPrimaryKeyField()));
}
/////////////////////////////////////
// let query manager do the update //
/////////////////////////////////////
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

@ -45,9 +45,9 @@ public class ConnectionManager
{ {
case "aurora": case "aurora":
{ {
//TODO AWS version not working and why ssl=false required? // TODO aws-mysql-jdbc driver not working when running on AWS
// jdbcURL = "jdbc:mysql:aws://" + backend.getHostName() + ":" + backend.getPort() + "/" + backend.getDatabaseName() + "?rewriteBatchedStatements=true&zeroDateTimeBehavior=CONVERT_TO_NULL";
jdbcURL = "jdbc:mysql://" + backend.getHostName() + ":" + backend.getPort() + "/" + backend.getDatabaseName() + "?rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull&useSSL=false"; jdbcURL = "jdbc:mysql://" + backend.getHostName() + ":" + backend.getPort() + "/" + backend.getDatabaseName() + "?rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull&useSSL=false";
//jdbcURL = "jdbc:mysql:aws://" + backend.getHostName() + ":" + backend.getPort() + "/" + backend.getDatabaseName() + "?rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull";
break; break;
} }
case "mysql": case "mysql":
@ -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,12 @@ 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.Collections;
import java.util.HashMap; 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,10 +55,17 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
*******************************************************************************/ *******************************************************************************/
public class QueryManager public class QueryManager
{ {
private static final int PAGE_SIZE = 2000; public 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;
private static boolean collectStatistics = false;
private static final Map<String, Integer> statistics = Collections.synchronizedMap(new HashMap<>());
public static final String STAT_QUERIES_RAN = "queriesRan";
public static final String STAT_BATCHES_RAN = "batchesRan";
/******************************************************************************* /*******************************************************************************
@ -83,12 +88,13 @@ 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
{ {
statement = prepareStatementAndBindParams(connection, sql, params); statement = prepareStatementAndBindParams(connection, sql, params);
statement.execute(); statement.execute();
incrementStatistic(STAT_QUERIES_RAN);
resultSet = statement.getResultSet(); resultSet = statement.getResultSet();
procesor.processResultSet(resultSet); procesor.processResultSet(resultSet);
@ -114,6 +120,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 +153,7 @@ public class QueryManager
resultSet.close(); resultSet.close();
} }
} }
*/
} }
@ -155,6 +164,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 +214,7 @@ public class QueryManager
{ {
return (null); return (null);
} }
*/
} }
@ -212,6 +224,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 +245,7 @@ public class QueryManager
{ {
return (null); return (null);
} }
*/
} }
@ -240,6 +255,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 +268,7 @@ public class QueryManager
{ {
return (null); return (null);
} }
*/
} }
@ -260,6 +278,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 +298,7 @@ public class QueryManager
} }
return (rs); return (rs);
*/
} }
@ -287,6 +308,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 +323,7 @@ public class QueryManager
} }
return (rs); return (rs);
*/
} }
@ -309,6 +333,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 +343,7 @@ public class QueryManager
row.put(metaData.getColumnName(i), getObject(resultSet, i)); row.put(metaData.getColumnName(i), getObject(resultSet, i));
} }
return row; return row;
*/
} }
@ -328,6 +355,7 @@ public class QueryManager
{ {
PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params); PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params);
statement.executeUpdate(); statement.executeUpdate();
incrementStatistic(STAT_QUERIES_RAN);
return (statement); return (statement);
} }
@ -340,6 +368,7 @@ public class QueryManager
{ {
PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params); PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params);
statement.executeUpdate(); statement.executeUpdate();
incrementStatistic(STAT_QUERIES_RAN);
return (statement); return (statement);
} }
@ -350,10 +379,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 +395,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();
} }
*/
} }
@ -379,6 +414,7 @@ public class QueryManager
try(PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params)) try(PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params))
{ {
statement.executeUpdate(); statement.executeUpdate();
incrementStatistic(STAT_QUERIES_RAN);
return (statement.getUpdateCount()); return (statement.getUpdateCount());
} }
} }
@ -390,11 +426,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 +443,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 +459,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
{ {
@ -433,6 +475,7 @@ public class QueryManager
bindParams(params.toArray(), statement); bindParams(params.toArray(), statement);
statement.executeUpdate(); statement.executeUpdate();
ResultSet generatedKeys = statement.getGeneratedKeys(); ResultSet generatedKeys = statement.getGeneratedKeys();
incrementStatistic(STAT_QUERIES_RAN);
while(generatedKeys.next()) while(generatedKeys.next())
{ {
rs.add(getInteger(generatedKeys, 1)); rs.add(getInteger(generatedKeys, 1));
@ -448,6 +491,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 +519,7 @@ public class QueryManager
page.clear(); page.clear();
} }
pages.clear(); pages.clear();
*/
} }
@ -483,6 +529,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 +541,33 @@ 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();
incrementStatistic(STAT_BATCHES_RAN);
}
} }
@ -570,12 +645,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 +703,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);
@ -670,24 +746,23 @@ 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 +806,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 +924,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 +1120,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 +1153,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 +1186,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 +1196,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 +1206,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 +1216,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 +1227,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 +1237,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 +1248,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 +1258,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 +1287,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 +1297,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 +1307,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 +1325,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 +1343,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 +1361,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 +1379,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 +1397,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 +1419,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 +1432,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 +1446,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 +1463,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 +1473,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 +1494,7 @@ public class QueryManager
{ {
return (findIdForTimeUnitAgo(connection, tableName, dateFieldName, goalUnitsAgo, minId, midId, unit)); return (findIdForTimeUnitAgo(connection, tableName, dateFieldName, goalUnitsAgo, minId, midId, unit));
} }
*/
} }
@ -1377,6 +1504,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 +1524,100 @@ 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>
// {
// 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;
// }
// /*******************************************************************************
// **
// *******************************************************************************/
// public T getValue()
// {
// return (value);
// }
// /*******************************************************************************
// **
// *******************************************************************************/
// public Class<T> getType()
// {
// return (type);
// }
// }
/*******************************************************************************
** Setter for collectStatistics
**
*******************************************************************************/
public static void setCollectStatistics(boolean collectStatistics)
{
QueryManager.collectStatistics = collectStatistics;
} }
/******************************************************************************* /*******************************************************************************
** Increment a statistic
** **
*******************************************************************************/ *******************************************************************************/
public static class TypeValuePair<T> public static void incrementStatistic(String statName)
{ {
private Class<T> type; if(collectStatistics)
private T value;
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("unchecked")
public TypeValuePair(T value)
{ {
this.value = value; statistics.putIfAbsent(statName, 0);
this.type = (Class<T>) value.getClass(); statistics.put(statName, statistics.get(statName) + 1);
} }
}
/******************************************************************************* /*******************************************************************************
** ** clear the map of statistics
*******************************************************************************/ **
public TypeValuePair(Class<T> type, T value) *******************************************************************************/
{ public static void resetStatistics()
this.type = type; {
this.value = value; statistics.clear();
} }
/******************************************************************************* /*******************************************************************************
** ** Getter for statistics
*******************************************************************************/ **
public T getValue() *******************************************************************************/
{ public static Map<String, Integer> getStatistics()
return (value); {
} return statistics;
/*******************************************************************************
**
*******************************************************************************/
public Class<T> getType()
{
return (type);
}
} }
} }

View File

@ -52,6 +52,18 @@ public class RDBMSBackendMetaData extends QBackendMetaData
/*******************************************************************************
** Fluent setter, override to help fluent flows
*******************************************************************************/
@Override
public RDBMSBackendMetaData withName(String name)
{
setName(name);
return this;
}
/******************************************************************************* /*******************************************************************************
** Getter for vendor ** Getter for vendor
** **

View File

@ -58,8 +58,7 @@ public class TestUtils
.withVendor("h2") .withVendor("h2")
.withHostName("mem") .withHostName("mem")
.withDatabaseName("test_database") .withDatabaseName("test_database")
.withUsername("sa") .withUsername("sa");
.withPassword("");
rdbmsBackendMetaData.setName("default"); rdbmsBackendMetaData.setName("default");
return (rdbmsBackendMetaData); return (rdbmsBackendMetaData);
} }

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

@ -0,0 +1,121 @@
/*
* 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.actions;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.count.CountRequest;
import com.kingsrook.qqq.backend.core.model.actions.count.CountResult;
import com.kingsrook.qqq.backend.core.model.actions.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/*******************************************************************************
**
*******************************************************************************/
public class RDBMSCountActionTest extends RDBMSActionTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
public void beforeEach() throws Exception
{
super.primeTestDatabase();
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testUnfilteredCount() throws QException
{
CountRequest countRequest = initCountRequest();
CountResult countResult = new RDBMSCountAction().execute(countRequest);
Assertions.assertEquals(5, countResult.getCount(), "Unfiltered query should find all rows");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testEqualsQueryCount() throws QException
{
String email = "darin.kelkhoff@gmail.com";
CountRequest countRequest = initCountRequest();
countRequest.setFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria()
.withFieldName("email")
.withOperator(QCriteriaOperator.EQUALS)
.withValues(List.of(email)))
);
CountResult countResult = new RDBMSCountAction().execute(countRequest);
Assertions.assertEquals(1, countResult.getCount(), "Expected # of rows");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testNotEqualsQuery() throws QException
{
String email = "darin.kelkhoff@gmail.com";
CountRequest countRequest = initCountRequest();
countRequest.setFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria()
.withFieldName("email")
.withOperator(QCriteriaOperator.NOT_EQUALS)
.withValues(List.of(email)))
);
CountResult countResult = new RDBMSCountAction().execute(countRequest);
Assertions.assertEquals(4, countResult.getCount(), "Expected # of rows");
}
/*******************************************************************************
**
*******************************************************************************/
private CountRequest initCountRequest()
{
CountRequest countRequest = new CountRequest();
countRequest.setInstance(TestUtils.defineInstance());
countRequest.setTableName(TestUtils.defineTablePerson().getName());
return countRequest;
}
}

View File

@ -22,7 +22,9 @@
package com.kingsrook.qqq.backend.module.rdbms.actions; package com.kingsrook.qqq.backend.module.rdbms.actions;
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.insert.InsertRequest; import com.kingsrook.qqq.backend.core.model.actions.insert.InsertRequest;
import com.kingsrook.qqq.backend.core.model.actions.insert.InsertResult; import com.kingsrook.qqq.backend.core.model.actions.insert.InsertResult;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -50,6 +52,34 @@ public class RDBMSInsertActionTest extends RDBMSActionTest
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testInsertNullList() throws QException
{
InsertRequest insertRequest = initInsertRequest();
insertRequest.setRecords(null);
InsertResult insertResult = new RDBMSInsertAction().execute(insertRequest);
assertEquals(0, insertResult.getRecords().size());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testInsertEmptyList() throws QException
{
InsertRequest insertRequest = initInsertRequest();
insertRequest.setRecords(Collections.emptyList());
InsertResult insertResult = new RDBMSInsertAction().execute(insertRequest);
assertEquals(0, insertResult.getRecords().size());
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -22,11 +22,17 @@
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 java.util.Map;
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;
import com.kingsrook.qqq.backend.module.rdbms.TestUtils; import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; 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;
@ -47,6 +53,49 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
public void beforeEach() throws Exception public void beforeEach() throws Exception
{ {
super.primeTestDatabase(); super.primeTestDatabase();
QueryManager.setCollectStatistics(true);
QueryManager.resetStatistics();
}
/*******************************************************************************
**
*******************************************************************************/
@AfterEach
public void afterEach() throws Exception
{
QueryManager.resetStatistics();
QueryManager.setCollectStatistics(false);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testUpdateNullList() throws QException
{
UpdateRequest updateRequest = initUpdateRequest();
updateRequest.setRecords(null);
UpdateResult updateResult = new RDBMSUpdateAction().execute(updateRequest);
assertEquals(0, updateResult.getRecords().size());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testUpdateEmptyList() throws QException
{
UpdateRequest updateRequest = initUpdateRequest();
updateRequest.setRecords(Collections.emptyList());
new RDBMSUpdateAction().execute(updateRequest);
UpdateResult updateResult = new RDBMSUpdateAction().execute(updateRequest);
assertEquals(0, updateResult.getRecords().size());
} }
@ -65,7 +114,11 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
.withValue("email", "jamestk@starfleet.net") .withValue("email", "jamestk@starfleet.net")
.withValue("birthDate", "2210-05-20"); .withValue("birthDate", "2210-05-20");
updateRequest.setRecords(List.of(record)); updateRequest.setRecords(List.of(record));
UpdateResult updateResult = new RDBMSUpdateAction().execute(updateRequest); UpdateResult updateResult = new RDBMSUpdateAction().execute(updateRequest);
Map<String, Integer> statistics = QueryManager.getStatistics();
assertEquals(1, statistics.get(QueryManager.STAT_QUERIES_RAN));
assertEquals(1, updateResult.getRecords().size(), "Should return 1 row"); assertEquals(1, updateResult.getRecords().size(), "Should return 1 row");
assertEquals(2, updateResult.getRecords().get(0).getValue("id"), "Should have id=2 in the row"); assertEquals(2, updateResult.getRecords().get(0).getValue("id"), "Should have id=2 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");
@ -94,7 +147,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 +161,24 @@ 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");
// this test runs one batch and one regular query
Map<String, Integer> statistics = QueryManager.getStatistics();
assertEquals(1, statistics.get(QueryManager.STAT_BATCHES_RAN));
assertEquals(1, statistics.get(QueryManager.STAT_QUERIES_RAN));
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 +203,110 @@ 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);
Map<String, Integer> statistics = QueryManager.getStatistics();
assertEquals(1, statistics.get(QueryManager.STAT_BATCHES_RAN));
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);
Map<String, Integer> statistics = QueryManager.getStatistics();
assertEquals(1, statistics.get(QueryManager.STAT_QUERIES_RAN));
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,113 @@
/*
* 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 java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/*******************************************************************************
** Unit test for ConnectionManager
*******************************************************************************/
@Disabled("This was okay for POC, but shouldn't run in CI")
class ConnectionManagerTest
{
@Test
public void test() throws SQLException
{
Connection connection = new ConnectionManager().getConnection(getAuroraBacked());
assertNotNull(connection);
String sql = """
insert into raw_parcel_invoice_line_ups (id, create_date, modify_date, parcel_invoice_line_id, account_country, account_number, account_split_payment_indicator, account_tax_id, alternate_invoice_amount, alternate_invoice_number, alternate_invoicing_currency_code, bol__number_1, bol__number_2, bol__number_3, bol__number_4, bol__number_5, basis_currency_code, basis_value, bill_option_code,
billed_weight, billed_weight_type, billed_weight_unit_of_measure, cccd_number, cpc_code, carrier_name_clinical_trial_identification_number__sds_id, charge_category_code, charge_category_detail_code, charge_classification_code, charge_description, charge_description_code, charge_source, charged_unit_quantity, class_number, contact_name, container_type,
corrected_zone, currency_variance_amount, customer_reference_number, customs_number, customs_office_name, cycle_date, cycle_number, declaration_number, declared_freight_class, detail_class, detail_keyed_billed_dimension, detail_keyed_billed_unit_of_measure, detail_keyed_dim, detail_keyed_unit_of_measure, direct_shipment_date, document_number, document_type,
duty_amount, duty_rate, duty_value, eft_date, eori_number, epu, entered_currency_code, entered_value, entered_weight, entered_weight_unit_of_measure, entry_date, entry_number, entry_port, entry_type, exchange_rate, excise_tax_amount, excise_tax_rate, export_place, foreign_trade_reference_number, freight_sequence_number, gst_amount, gst_rate,
goods_description, import_tax_id, incentive_amount, invoice_amount, invoice_currency_code, invoice_date, invoice_due_date, invoice_exchange_rate, invoice_level_charge, invoice_number, invoice_remit_amount, invoice_type_code, invoice_type_detail_code, item_quantity, item_quantity_unit_of_measure, job_number, lead_shipment_number, line_item_number,
master_air_waybill_number, miscellaneous_address_1_address_line_1, miscellaneous_address_1_address_line_2, miscellaneous_address_1_city, miscellaneous_address_1_company_name, miscellaneous_address_1_country, miscellaneous_address_1_name, miscellaneous_address_1_postal, miscellaneous_address_1_state, miscellaneous_address_2_address_line_1,
miscellaneous_address_2_address_line_2, miscellaneous_address_2_city, miscellaneous_address_2_company_name, miscellaneous_address_2_country, miscellaneous_address_2_name, miscellaneous_address_2_postal, miscellaneous_address_2_state, miscellaneous_address_qual_1, miscellaneous_address_qual_2, miscellaneous_currency_code, miscellaneous_incentive_amount,
miscellaneous_line_1, miscellaneous_line_10, miscellaneous_line_11, miscellaneous_line_2, miscellaneous_line_3, miscellaneous_line_4, miscellaneous_line_5, miscellaneous_line_7, miscellaneous_line_8, miscellaneous_line_9, miscellaneous_net_amount, nmfc, net_amount, office_number, order_in_council, origin_country, original_service_description,
original_shipment_package_quantity, original_tracking_number, other_amount, other_basis_amount, other_customs_number, other_customs_number_indicator, other_rate, oversize_quantity, po__number_1, po__number_10, po__number_2, po__number_3, po__number_4, po__number_5, po__number_6, po__number_7, po__number_8, po__number_9, package_dimension_unit_of_measure,
package_dimensions, package_quantity, package_reference_number_1, package_reference_number_2, package_reference_number_3, package_reference_number_4, package_reference_number_5, payer_role_code, pickup_record_number, place_holder_46, place_holder_47, place_holder_48, place_holder_52, place_holder_53, place_holder_54, place_holder_55, place_holder_56,
place_holder_57, place_holder_58, place_holder_59, promo_discount_alias, promo_discount_applied_indicator, raw_dimension_unit_of_measure, raw_dimensions, receiver_address_line_1, receiver_address_line_2, receiver_city, receiver_company_name, receiver_country, receiver_name, receiver_postal, receiver_state, recipient_number, scc_scale_weight,
sds_delivery_date, sds_error_code, sds_match_level_cd, sds_rnr_date, sima_access, scale_weight_unit_of_measure, scale_weight_quantity, sender_address_line_1, sender_address_line_2, sender_city, sender_company_name, sender_country, sender_name, sender_postal, sender_state, shipment_date, shipment_delivery_date, shipment_description, shipment_export_date,
shipment_import_date, shipment_reference_number_1, shipment_reference_number_2, shipment_release_date, shipment_value_amount, sold_to_address_line_1, sold_to_address_line_2, sold_to_city, sold_to_company_name, sold_to_country, sold_to_name, sold_to_postal, sold_to_state, store_number, tariff_code, tariff_rate, tariff_treatment_number, tax_indicator,
tax_type, tax_value, tax_variance_amount, tax_law_article_basis_amount, tax_law_article_number, third_party_address_line_1, third_party_address_line_2, third_party_city, third_party_company_name, third_party_country, third_party_name, third_party_postal, third_party_state, total_customs_amount, total_value_for_duty, tracking_number,
transaction_currency_code, transaction_date, transport_mode, type_code_1, type_code_2, type_detail_code_1, type_detail_code_2, type_detail_value_1, type_detail_value_2, unit_of_measure, vat_amount, vat_basis_amount, vat_rate, validation_date, version, weight, world_ease_number, zone, data_lake_id)
values
""";
String values = """
(0, '2022-06-13 13:07:52.0', '2022-06-13 13:07:52.0', null, 'US', '00000F2098', null, null, 0, null, null, null, null, null, null, null, null, 0.00, null, 0, null, null, null, null,
null, 'MIS', 'SVCH', 'FRT', 'Service Charge', null, null, 0, null, null, null, null, 0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0, 0, 0, null, null, null, null, 0,
0, null, null, null, null, null, 0, 0, 0, null, null, 0, 0, 0, null, null, 0.00, 36, 'USD', '2022-01-01', '2022-01-10', 0, 0, '0000000F2098012', null, 'E', null, 0, null, null, null,
0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0, null, null, null, null, null, null, null, null, null, null, 0, null, 36.00, null, null, null, null, 0, null, 0,
0, null, null, 0, 0, null, null, null, null, null, null, null, null, null, null, null, null, 0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, '00000F2098', 0, null, null, null, null, 0, null, 0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0, null, null, null, null, null, null, null, null, null, null,
0, null, null, null, 0, 0, 0, null, null, null, null, null, null, null, null, null, 0, 0, null, 'USD', '2022-01-01', null, null, null, null, null, null, null, null, 0, 0, 0, null, 2, null, null, null, 'XXXXXX')
""";
sql += String.join(",", Collections.nCopies(1000, values));
for(int i = 0; i < 10; i++)
{
System.out.println("== Cycle " + i);
QueryManager.executeUpdate(connection, "BEGIN WORK");
System.out.println("Begin work...");
Integer insertCount = QueryManager.executeUpdateForRowCount(connection, sql);
System.out.println("Inserted: " + insertCount);
Integer deleteCount = QueryManager.executeUpdateForRowCount(connection, "DELETE from raw_parcel_invoice_line_ups WHERE data_lake_id='XXXXXX'");
System.out.println("Deleted: " + deleteCount);
boolean commit = true;
if(commit)
{
QueryManager.executeUpdate(connection, "COMMIT WORK");
System.out.println("Commit.");
}
else
{
QueryManager.executeUpdate(connection, "ROLLBACK WORK");
System.out.println("Rollback.");
}
}
}
private RDBMSBackendMetaData getAuroraBacked()
{
return new RDBMSBackendMetaData()
.withName("aurora-test")
.withVendor("aurora")
.withHostName("nf-one-development-aurora.cwuhqcx1inwx.us-east-2.rds.amazonaws.com")
.withPort(3306)
.withDatabaseName("nutrifresh_one")
.withUsername("nf_admin")
.withPassword("%!2rwcH+fb#WgPg");
}
}

View File

@ -49,7 +49,7 @@ class RDBMSBackendMetaDataTest
System.out.println(JsonUtils.prettyPrint(json)); System.out.println(JsonUtils.prettyPrint(json));
System.out.println(json); System.out.println(json);
String expectToContain = """ String expectToContain = """
"backends":{"default":{"hostName":"mem","password":"","databaseName":"test_database\""""; "backends":{"default":{"hostName":"mem","databaseName":"test_database\"""";
assertTrue(json.contains(expectToContain)); assertTrue(json.contains(expectToContain));
} }

View File

@ -37,3 +37,24 @@ INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (2, 'Ja
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com'); INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com');
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com'); INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com');
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com'); INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com');
DROP TABLE IF EXISTS carrier;
CREATE TABLE carrier
(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(80) NOT NULL,
company_code VARCHAR(80) NOT NULL,
service_level VARCHAR(80) NOT NULL
);
INSERT INTO carrier (id, name, company_code, service_level) VALUES (1, 'UPS Ground', 'UPS', 'G');
INSERT INTO carrier (id, name, company_code, service_level) VALUES (2, 'UPS 2Day', 'UPS', '2');
INSERT INTO carrier (id, name, company_code, service_level) VALUES (3, 'UPS International', 'UPS', 'I');
INSERT INTO carrier (id, name, company_code, service_level) VALUES (4, 'Fedex Ground', 'FEDEX', 'G');
INSERT INTO carrier (id, name, company_code, service_level) VALUES (5, 'Fedex Next Day', 'UPS', '1');
INSERT INTO carrier (id, name, company_code, service_level) VALUES (6, 'Will Call', 'WILL_CALL', 'W');
INSERT INTO carrier (id, name, company_code, service_level) VALUES (7, 'USPS Priority', 'USPS', '1');
INSERT INTO carrier (id, name, company_code, service_level) VALUES (8, 'USPS Super Slow', 'USPS', '4');
INSERT INTO carrier (id, name, company_code, service_level) VALUES (9, 'USPS Super Fast', 'USPS', '0');
INSERT INTO carrier (id, name, company_code, service_level) VALUES (10, 'DHL International', 'DHL', 'I');
INSERT INTO carrier (id, name, company_code, service_level) VALUES (11, 'GSO', 'GSO', 'G');