Post-sprint-7 cleanup -- LocalDate off-by-offset; moved mysql pStatement optimization

This commit is contained in:
2022-07-28 08:31:16 -05:00
parent a08d780302
commit 596d5bdfe3
4 changed files with 136 additions and 44 deletions

View File

@ -25,7 +25,6 @@ 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.actions.interfaces.CountInterface;
@ -76,7 +75,6 @@ public class RDBMSCountAction extends AbstractRDBMSAction implements CountInterf
{
QueryManager.executeStatement(connection, sql, ((ResultSet resultSet) ->
{
ResultSetMetaData metaData = resultSet.getMetaData();
if(resultSet.next())
{
rs.setCount(resultSet.getInt("record_count"));

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.module.rdbms.actions;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
@ -42,6 +43,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -101,12 +103,12 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
try(Connection connection = getConnection(queryInput))
{
QueryManager.executeStatement(connection, sql, ((ResultSet resultSet) ->
PreparedStatement statement = createStatement(connection, sql, queryInput);
QueryManager.executeStatement(statement, ((ResultSet resultSet) ->
{
ResultSetMetaData metaData = resultSet.getMetaData();
while(resultSet.next())
{
// todo - should refactor this for view etc to use too.
// todo - Add display values (String labels for possibleValues, formatted #'s, etc)
QRecord record = new QRecord();
record.setTableName(table.getName());
@ -137,6 +139,32 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
/*******************************************************************************
**
*******************************************************************************/
private PreparedStatement createStatement(Connection connection, String sql, QueryInput queryInput) throws SQLException
{
RDBMSBackendMetaData backend = (RDBMSBackendMetaData) queryInput.getBackend();
PreparedStatement statement;
if("mysql".equals(backend.getVendor()))
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// mysql "optimization", presumably here - from Result Set section of https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-implementation-notes.html //
// without this change, we saw ~10 seconds of "wait" time, before results would start to stream out of a large query (e.g., > 1,000,000 rows). //
// with this change, we start to get results immediately, and the total runtime also seems lower... //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
statement = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
statement.setFetchSize(Integer.MIN_VALUE);
}
else
{
statement = connection.prepareStatement(sql);
}
return (statement);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -87,19 +87,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 processor, Object... params) throws SQLException
{
PreparedStatement statement = null;
ResultSet resultSet = null;
try
{
statement = prepareStatementAndBindParams(connection, sql, params);
incrementStatistic(STAT_QUERIES_RAN);
statement.execute();
resultSet = statement.getResultSet();
procesor.processResultSet(resultSet);
executeStatement(statement, processor, params);
}
finally
{
@ -107,7 +101,30 @@ public class QueryManager
{
statement.close();
}
}
}
/*******************************************************************************
** Let the caller provide their own prepared statement (e.g., possibly with some
** customized settings/optimizations).
*******************************************************************************/
public static void executeStatement(PreparedStatement statement, ResultSetProcessor processor, Object... params) throws SQLException
{
ResultSet resultSet = null;
try
{
bindParams(statement, params);
incrementStatistic(STAT_QUERIES_RAN);
statement.execute();
resultSet = statement.getResultSet();
processor.processResultSet(resultSet);
}
finally
{
if(resultSet != null)
{
resultSet.close();
@ -653,34 +670,34 @@ public class QueryManager
return (1);
}
else*/
if(value instanceof Integer)
if(value instanceof Integer i)
{
bindParam(statement, index, (Integer) value);
bindParam(statement, index, i);
return (1);
}
else if(value instanceof Short)
else if(value instanceof Short s)
{
bindParam(statement, index, ((Short) value).intValue());
bindParam(statement, index, s.intValue());
return (1);
}
else if(value instanceof Long)
else if(value instanceof Long l)
{
bindParam(statement, index, ((Long) value).intValue());
bindParam(statement, index, l.intValue());
return (1);
}
else if(value instanceof String)
else if(value instanceof String s)
{
bindParam(statement, index, (String) value);
bindParam(statement, index, s);
return (1);
}
else if(value instanceof Boolean)
else if(value instanceof Boolean b)
{
bindParam(statement, index, (Boolean) value);
bindParam(statement, index, b);
return (1);
}
else if(value instanceof Timestamp)
else if(value instanceof Timestamp ts)
{
bindParam(statement, index, (Timestamp) value);
bindParam(statement, index, ts);
return (1);
}
else if(value instanceof Date)
@ -688,14 +705,14 @@ public class QueryManager
bindParam(statement, index, (Date) value);
return (1);
}
else if(value instanceof Calendar)
else if(value instanceof Calendar c)
{
bindParam(statement, index, (Calendar) value);
bindParam(statement, index, c);
return (1);
}
else if(value instanceof BigDecimal)
else if(value instanceof BigDecimal bd)
{
bindParam(statement, index, (BigDecimal) value);
bindParam(statement, index, bd);
return (1);
}
else if(value == null)
@ -703,42 +720,47 @@ public class QueryManager
statement.setNull(index, Types.CHAR);
return (1);
}
else if(value instanceof Collection)
else if(value instanceof Collection c)
{
Collection<?> collection = (Collection<?>) value;
int paramsBound = 0;
for(Object o : collection)
int paramsBound = 0;
for(Object o : c)
{
paramsBound += bindParamObject(statement, (index + paramsBound), o);
}
return (paramsBound);
}
else if(value instanceof byte[])
else if(value instanceof byte[] ba)
{
statement.setBytes(index, (byte[]) value);
statement.setBytes(index, ba);
return (1);
}
else if(value instanceof Instant)
else if(value instanceof Instant i)
{
Timestamp timestamp = new Timestamp(((Instant) value).toEpochMilli());
long epochMillis = i.toEpochMilli();
Timestamp timestamp = new Timestamp(epochMillis);
statement.setTimestamp(index, timestamp);
return (1);
}
else if(value instanceof LocalDate)
else if(value instanceof LocalDate ld)
{
Timestamp timestamp = new Timestamp(((LocalDate) value).atTime(0, 0).toEpochSecond(ZoneOffset.UTC) * MS_PER_SEC);
ZoneOffset offset = OffsetDateTime.now().getOffset();
long epochMillis = ld.atStartOfDay().toEpochSecond(offset) * MS_PER_SEC;
Timestamp timestamp = new Timestamp(epochMillis);
statement.setTimestamp(index, timestamp);
return (1);
}
else if(value instanceof OffsetDateTime)
else if(value instanceof OffsetDateTime odt)
{
Timestamp timestamp = new Timestamp(((OffsetDateTime) value).toEpochSecond() * MS_PER_SEC);
long epochMillis = odt.toEpochSecond() * MS_PER_SEC;
Timestamp timestamp = new Timestamp(epochMillis);
statement.setTimestamp(index, timestamp);
return (1);
}
else if(value instanceof LocalDateTime)
else if(value instanceof LocalDateTime ldt)
{
Timestamp timestamp = new Timestamp(((LocalDateTime) value).toEpochSecond(ZoneOffset.UTC) * MS_PER_SEC);
ZoneOffset offset = OffsetDateTime.now().getOffset();
long epochMillis = ldt.toEpochSecond(offset) * MS_PER_SEC;
Timestamp timestamp = new Timestamp(epochMillis);
statement.setTimestamp(index, timestamp);
return (1);
}

View File

@ -32,6 +32,7 @@ import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.OffsetDateTime;
import java.util.GregorianCalendar;
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
@ -58,7 +59,7 @@ class QueryManagerTest
void beforeEach() throws SQLException
{
Connection connection = getConnection();
QueryManager.executeUpdate(connection, "CREATE TABLE t (i INTEGER, dt DATETIME, c CHAR(1))");
QueryManager.executeUpdate(connection, "CREATE TABLE t (i INTEGER, dt DATETIME, c CHAR(1), d DATE)");
}
@ -86,7 +87,8 @@ class QueryManagerTest
/*******************************************************************************
** Test the various overloads that bind params
** Test the various overloads that bind params.
** Note, we're just confirming that these methods don't throw...
*******************************************************************************/
@Test
void testBindParams() throws SQLException
@ -224,4 +226,46 @@ class QueryManagerTest
assertNull(QueryManager.getObject(rs, 3));
}
/*******************************************************************************
** We had a bug where LocalDates weren't being properly bound. This test
** confirms (more?) correct behavior
*******************************************************************************/
@Test
void testLocalDate() throws SQLException
{
Connection connection = getConnection();
QueryManager.executeUpdate(connection, "INSERT INTO t (d) VALUES (?)", LocalDate.of(2013, Month.OCTOBER, 1));
PreparedStatement preparedStatement = connection.prepareStatement("SELECT d from t");
preparedStatement.execute();
ResultSet rs = preparedStatement.getResultSet();
rs.next();
Date date = QueryManager.getDate(rs, 1);
assertEquals(1, date.getDate(), "Date value");
assertEquals(Month.OCTOBER.getValue(), date.getMonth() + 1, "Month value");
assertEquals(2013, date.getYear() + 1900, "Year value");
LocalDate localDate = QueryManager.getLocalDate(rs, 1);
assertEquals(1, localDate.getDayOfMonth(), "Date value");
assertEquals(Month.OCTOBER, localDate.getMonth(), "Month value");
assertEquals(2013, localDate.getYear(), "Year value");
LocalDateTime localDateTime = QueryManager.getLocalDateTime(rs, 1);
assertEquals(1, localDateTime.getDayOfMonth(), "Date value");
assertEquals(Month.OCTOBER, localDateTime.getMonth(), "Month value");
assertEquals(2013, localDateTime.getYear(), "Year value");
assertEquals(0, localDateTime.getHour(), "Hour value");
assertEquals(0, localDateTime.getMinute(), "Minute value");
OffsetDateTime offsetDateTime = QueryManager.getOffsetDateTime(rs, 1);
assertEquals(1, offsetDateTime.getDayOfMonth(), "Date value");
assertEquals(Month.OCTOBER, offsetDateTime.getMonth(), "Month value");
assertEquals(2013, offsetDateTime.getYear(), "Year value");
assertEquals(0, offsetDateTime.getHour(), "Hour value");
assertEquals(0, offsetDateTime.getMinute(), "Minute value");
}
}