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.io.Serializable;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface; 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) -> QueryManager.executeStatement(connection, sql, ((ResultSet resultSet) ->
{ {
ResultSetMetaData metaData = resultSet.getMetaData();
if(resultSet.next()) if(resultSet.next())
{ {
rs.setCount(resultSet.getInt("record_count")); 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.io.Serializable;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.sql.SQLException; 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.model.metadata.tables.QTableMetaData;
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 com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -101,12 +103,12 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
try(Connection connection = getConnection(queryInput)) 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(); ResultSetMetaData metaData = resultSet.getMetaData();
while(resultSet.next()) while(resultSet.next())
{ {
// todo - should refactor this for view etc to use too.
// todo - Add display values (String labels for possibleValues, formatted #'s, etc) // todo - Add display values (String labels for possibleValues, formatted #'s, etc)
QRecord record = new QRecord(); QRecord record = new QRecord();
record.setTableName(table.getName()); 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; PreparedStatement statement = null;
ResultSet resultSet = null;
try try
{ {
statement = prepareStatementAndBindParams(connection, sql, params); statement = prepareStatementAndBindParams(connection, sql, params);
incrementStatistic(STAT_QUERIES_RAN); executeStatement(statement, processor, params);
statement.execute();
resultSet = statement.getResultSet();
procesor.processResultSet(resultSet);
} }
finally finally
{ {
@ -107,7 +101,30 @@ public class QueryManager
{ {
statement.close(); 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) if(resultSet != null)
{ {
resultSet.close(); resultSet.close();
@ -653,34 +670,34 @@ public class QueryManager
return (1); return (1);
} }
else*/ else*/
if(value instanceof Integer) if(value instanceof Integer i)
{ {
bindParam(statement, index, (Integer) value); bindParam(statement, index, i);
return (1); 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); 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); return (1);
} }
else if(value instanceof String) else if(value instanceof String s)
{ {
bindParam(statement, index, (String) value); bindParam(statement, index, s);
return (1); return (1);
} }
else if(value instanceof Boolean) else if(value instanceof Boolean b)
{ {
bindParam(statement, index, (Boolean) value); bindParam(statement, index, b);
return (1); return (1);
} }
else if(value instanceof Timestamp) else if(value instanceof Timestamp ts)
{ {
bindParam(statement, index, (Timestamp) value); bindParam(statement, index, ts);
return (1); return (1);
} }
else if(value instanceof Date) else if(value instanceof Date)
@ -688,14 +705,14 @@ public class QueryManager
bindParam(statement, index, (Date) value); bindParam(statement, index, (Date) value);
return (1); return (1);
} }
else if(value instanceof Calendar) else if(value instanceof Calendar c)
{ {
bindParam(statement, index, (Calendar) value); bindParam(statement, index, c);
return (1); return (1);
} }
else if(value instanceof BigDecimal) else if(value instanceof BigDecimal bd)
{ {
bindParam(statement, index, (BigDecimal) value); bindParam(statement, index, bd);
return (1); return (1);
} }
else if(value == null) else if(value == null)
@ -703,42 +720,47 @@ public class QueryManager
statement.setNull(index, Types.CHAR); statement.setNull(index, Types.CHAR);
return (1); return (1);
} }
else if(value instanceof Collection) else if(value instanceof Collection c)
{ {
Collection<?> collection = (Collection<?>) value; int paramsBound = 0;
int paramsBound = 0; for(Object o : c)
for(Object o : collection)
{ {
paramsBound += bindParamObject(statement, (index + paramsBound), o); paramsBound += bindParamObject(statement, (index + paramsBound), o);
} }
return (paramsBound); return (paramsBound);
} }
else if(value instanceof byte[]) else if(value instanceof byte[] ba)
{ {
statement.setBytes(index, (byte[]) value); statement.setBytes(index, ba);
return (1); 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); statement.setTimestamp(index, timestamp);
return (1); 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); statement.setTimestamp(index, timestamp);
return (1); 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); statement.setTimestamp(index, timestamp);
return (1); 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); statement.setTimestamp(index, timestamp);
return (1); return (1);
} }

View File

@ -32,6 +32,7 @@ import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.Month;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import com.kingsrook.qqq.backend.module.rdbms.TestUtils; import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
@ -58,7 +59,7 @@ class QueryManagerTest
void beforeEach() throws SQLException void beforeEach() throws SQLException
{ {
Connection connection = getConnection(); 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 @Test
void testBindParams() throws SQLException void testBindParams() throws SQLException
@ -224,4 +226,46 @@ class QueryManagerTest
assertNull(QueryManager.getObject(rs, 3)); 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");
}
} }