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.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"));
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user