package org.hibernate.result.internal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import org.hibernate.JDBCException;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.CoreLogging;
import org.hibernate.loader.EntityAliases;
import org.hibernate.loader.custom.CustomLoader;
import org.hibernate.loader.custom.CustomQuery;
import org.hibernate.loader.custom.Return;
import org.hibernate.loader.custom.RootReturn;
import org.hibernate.loader.custom.sql.SQLQueryReturnProcessor;
import org.hibernate.param.ParameterBinder;
import org.hibernate.result.NoMoreReturnsException;
import org.hibernate.result.Output;
import org.hibernate.result.Outputs;
import org.hibernate.result.spi.ResultContext;
import org.jboss.logging.Logger;
public class OutputsImpl implements Outputs {
private static final Logger log = CoreLogging.logger( OutputsImpl.class );
private final ResultContext context;
private final PreparedStatement jdbcStatement;
private final CustomLoaderExtension loader;
private CurrentReturnState currentReturnState;
public OutputsImpl(ResultContext context, PreparedStatement jdbcStatement) {
this.context = context;
this.jdbcStatement = jdbcStatement;
this.loader = buildSpecializedCustomLoader( context );
try {
final boolean isResultSet = jdbcStatement.execute();
currentReturnState = buildCurrentReturnState( isResultSet );
}
catch (SQLException e) {
throw convert( e, "Error calling CallableStatement.getMoreResults" );
}
}
private CurrentReturnState buildCurrentReturnState(boolean isResultSet) {
int updateCount = -1;
if ( ! isResultSet ) {
try {
updateCount = jdbcStatement.getUpdateCount();
}
catch (SQLException e) {
throw convert( e, "Error calling CallableStatement.getUpdateCount" );
}
}
return buildCurrentReturnState( isResultSet, updateCount );
}
protected CurrentReturnState buildCurrentReturnState(boolean isResultSet, int updateCount) {
return new CurrentReturnState( isResultSet, updateCount );
}
protected JDBCException convert(SQLException e, String message) {
return context.getSession().getJdbcServices().getSqlExceptionHelper().convert(
e,
message,
context.getSql()
);
}
@Override
public Output getCurrent() {
if ( currentReturnState == null ) {
return null;
}
return currentReturnState.getOutput();
}
@Override
public boolean goToNext() {
if ( currentReturnState == null ) {
return false;
}
if ( currentReturnState.indicatesMoreOutputs() ) {
try {
final boolean isResultSet = jdbcStatement.getMoreResults();
currentReturnState = buildCurrentReturnState( isResultSet );
}
catch (SQLException e) {
throw convert( e, "Error calling CallableStatement.getMoreResults" );
}
}
return currentReturnState != null && currentReturnState.indicatesMoreOutputs();
}
@Override
public void release() {
try {
jdbcStatement.close();
}
catch (SQLException e) {
log.debug( "Unable to close PreparedStatement", e );
}
}
private List () {
try {
return extractResults( jdbcStatement.getResultSet() );
}
catch (SQLException e) {
throw convert( e, "Error calling CallableStatement.getResultSet" );
}
}
protected List (ResultSet resultSet) {
try {
return loader.processResultSet( resultSet );
}
catch (SQLException e) {
throw convert( e, "Error extracting results from CallableStatement" );
}
}
protected class CurrentReturnState {
private final boolean isResultSet;
private final int updateCount;
private Output rtn;
protected CurrentReturnState(boolean isResultSet, int updateCount) {
this.isResultSet = isResultSet;
this.updateCount = updateCount;
}
public boolean indicatesMoreOutputs() {
return isResultSet() || getUpdateCount() >= 0;
}
public boolean isResultSet() {
return isResultSet;
}
public int getUpdateCount() {
return updateCount;
}
public Output getOutput() {
if ( rtn == null ) {
rtn = buildOutput();
}
return rtn;
}
protected Output buildOutput() {
if ( log.isDebugEnabled() ) {
log.debugf(
"Building Return [isResultSet=%s, updateCount=%s, extendedReturn=%s",
isResultSet(),
getUpdateCount(),
hasExtendedReturns()
);
}
if ( isResultSet() ) {
return buildResultSetOutput( extractCurrentResults() );
}
else if ( getUpdateCount() >= 0 ) {
return buildUpdateCountOutput( updateCount );
}
else if ( hasExtendedReturns() ) {
return buildExtendedReturn();
}
throw new NoMoreReturnsException();
}
protected Output buildResultSetOutput(List list) {
return new ResultSetOutputImpl( list );
}
protected Output buildResultSetOutput(Supplier<List> listSupplier) {
return new ResultSetOutputImpl( listSupplier );
}
protected Output buildUpdateCountOutput(int updateCount) {
return new UpdateCountOutputImpl( updateCount );
}
protected boolean hasExtendedReturns() {
return false;
}
protected Output buildExtendedReturn() {
throw new IllegalStateException( "State does not define extended returns" );
}
}
private static CustomLoaderExtension buildSpecializedCustomLoader(final ResultContext context) {
final SQLQueryReturnProcessor processor = new SQLQueryReturnProcessor(
context.getQueryReturns(),
context.getSession().getFactory()
);
processor.process();
final List<org.hibernate.loader.custom.Return> customReturns = processor.generateCallableReturns();
CustomQuery customQuery = new CustomQuery() {
@Override
public String getSQL() {
return context.getSql();
}
@Override
public Set<String> getQuerySpaces() {
return context.getSynchronizedQuerySpaces();
}
@Override
public List<ParameterBinder> getParameterValueBinders() {
return Collections.emptyList();
}
@Override
public List<org.hibernate.loader.custom.Return> getCustomQueryReturns() {
return customReturns;
}
};
return new CustomLoaderExtension(
customQuery,
context.getQueryParameters(),
context.getSession()
);
}
private static class CustomLoaderExtension extends CustomLoader {
private static final EntityAliases[] NO_ALIASES = new EntityAliases[0];
private final QueryParameters queryParameters;
private final SharedSessionContractImplementor session;
private final EntityAliases[] entityAliases;
private boolean needsDiscovery = true;
public CustomLoaderExtension(
CustomQuery customQuery,
QueryParameters queryParameters,
SharedSessionContractImplementor session) {
super( customQuery, session.getFactory() );
this.queryParameters = queryParameters;
this.session = session;
entityAliases = interpretEntityAliases( customQuery.getCustomQueryReturns() );
}
private EntityAliases[] interpretEntityAliases(List<Return> customQueryReturns) {
final List<EntityAliases> entityAliases = new ArrayList<>();
for ( Return queryReturn : customQueryReturns ) {
if ( !RootReturn.class.isInstance( queryReturn ) ) {
continue;
}
entityAliases.add( ( (RootReturn) queryReturn ).getEntityAliases() );
}
if ( entityAliases.isEmpty() ) {
return NO_ALIASES;
}
return entityAliases.toArray( new EntityAliases[ entityAliases.size() ] );
}
@Override
protected EntityAliases[] getEntityAliases() {
return entityAliases;
}
public List processResultSet(ResultSet resultSet) throws SQLException {
if ( needsDiscovery ) {
super.autoDiscoverTypes( resultSet );
needsDiscovery = false;
}
return super.processResultSet(
resultSet,
queryParameters,
session,
true,
null,
Integer.MAX_VALUE,
Collections.emptyList()
);
}
}
}