mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Post-sprint-7 cleanup -- LocalDate off-by-offset; moved mysql pStatement optimization
This commit is contained in:
@ -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"));
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user