/* * Hibernate OGM, Domain model persistence for NoSQL datastores * * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.ogm.datastore.ignite; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import org.apache.ignite.IgniteAtomicSequence; import org.apache.ignite.IgniteCache; import org.apache.ignite.binary.BinaryObject; import org.apache.ignite.binary.BinaryObjectBuilder; import org.apache.ignite.cache.query.SqlFieldsQuery; import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.dialect.lock.LockingStrategy; import org.hibernate.dialect.lock.OptimisticForceIncrementLockingStrategy; import org.hibernate.dialect.lock.OptimisticLockingStrategy; import org.hibernate.dialect.lock.PessimisticForceIncrementLockingStrategy; import org.hibernate.loader.custom.ScalarReturn; import org.hibernate.ogm.datastore.ignite.impl.IgniteAssociationRowSnapshot; import org.hibernate.ogm.datastore.ignite.impl.IgniteAssociationSnapshot; import org.hibernate.ogm.datastore.ignite.impl.IgniteDatastoreProvider; import org.hibernate.ogm.datastore.ignite.impl.IgniteEmbeddedAssociationSnapshot; import org.hibernate.ogm.datastore.ignite.impl.IgniteTupleSnapshot; import org.hibernate.ogm.datastore.ignite.logging.impl.Log; import org.hibernate.ogm.datastore.ignite.logging.impl.LoggerFactory; import org.hibernate.ogm.datastore.ignite.options.impl.CollocatedAssociationOption; import org.hibernate.ogm.datastore.ignite.query.impl.IgniteParameterMetadataBuilder; import org.hibernate.ogm.datastore.ignite.query.impl.IgniteQueryDescriptor; import org.hibernate.ogm.datastore.ignite.query.impl.IgniteSqlQueryParser; import org.hibernate.ogm.datastore.ignite.query.impl.QueryHints; import org.hibernate.ogm.datastore.ignite.type.impl.IgniteGridTypeMapper; import org.hibernate.ogm.datastore.ignite.util.StringHelper; import org.hibernate.ogm.datastore.map.impl.MapTupleSnapshot; import org.hibernate.ogm.dialect.multiget.spi.MultigetGridDialect; import org.hibernate.ogm.dialect.query.spi.BackendQuery; import org.hibernate.ogm.dialect.query.spi.ClosableIterator; import org.hibernate.ogm.dialect.query.spi.ParameterMetadataBuilder; import org.hibernate.ogm.dialect.query.spi.QueryParameters; import org.hibernate.ogm.dialect.query.spi.QueryableGridDialect; import org.hibernate.ogm.dialect.query.spi.RowSelection; import org.hibernate.ogm.dialect.query.spi.TypedGridValue; import org.hibernate.ogm.dialect.spi.AssociationContext; import org.hibernate.ogm.dialect.spi.AssociationTypeContext; import org.hibernate.ogm.dialect.spi.BaseGridDialect; import org.hibernate.ogm.dialect.spi.GridDialect; import org.hibernate.ogm.dialect.spi.ModelConsumer; import org.hibernate.ogm.dialect.spi.NextValueRequest; import org.hibernate.ogm.dialect.spi.OperationContext; import org.hibernate.ogm.dialect.spi.TupleAlreadyExistsException; import org.hibernate.ogm.dialect.spi.TupleContext; import org.hibernate.ogm.dialect.spi.TupleTypeContext; import org.hibernate.ogm.entityentry.impl.TuplePointer; import org.hibernate.ogm.model.key.spi.AssociationKey; import org.hibernate.ogm.model.key.spi.AssociationKeyMetadata; import org.hibernate.ogm.model.key.spi.AssociationKind; import org.hibernate.ogm.model.key.spi.AssociationType; import org.hibernate.ogm.model.key.spi.EntityKey; import org.hibernate.ogm.model.key.spi.EntityKeyMetadata; import org.hibernate.ogm.model.key.spi.RowKey; import org.hibernate.ogm.model.spi.Association; import org.hibernate.ogm.model.spi.AssociationOperation; import org.hibernate.ogm.model.spi.AssociationOperationType; import org.hibernate.ogm.model.spi.AssociationSnapshot; import org.hibernate.ogm.model.spi.Tuple; import org.hibernate.ogm.model.spi.Tuple.SnapshotType; import org.hibernate.ogm.model.spi.TupleSnapshot; import org.hibernate.ogm.type.spi.GridType; import org.hibernate.ogm.util.impl.Contracts; import org.hibernate.persister.entity.Lockable; import org.hibernate.type.Type; public class IgniteDialect extends BaseGridDialect implements GridDialect, MultigetGridDialect, QueryableGridDialect<IgniteQueryDescriptor> { private static final Log log = LoggerFactory.getLogger(); private IgniteDatastoreProvider provider; public IgniteDialect(IgniteDatastoreProvider provider) { this.provider = provider; } @Override public LockingStrategy getLockingStrategy(Lockable lockable, LockMode lockMode) { if ( lockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT ) { return new PessimisticForceIncrementLockingStrategy( lockable, lockMode ); } // else if ( lockMode==LockMode.PESSIMISTIC_WRITE ) { // return new PessimisticWriteLockingStrategy( lockable, lockMode ); // } else if ( lockMode == LockMode.PESSIMISTIC_READ ) { return new IgnitePessimisticReadLockingStrategy( lockable, lockMode, provider ); } else if ( lockMode == LockMode.OPTIMISTIC ) { return new OptimisticLockingStrategy( lockable, lockMode ); } else if ( lockMode == LockMode.OPTIMISTIC_FORCE_INCREMENT ) { return new OptimisticForceIncrementLockingStrategy( lockable, lockMode ); } else { return null; } } @Override public Tuple getTuple(EntityKey key, OperationContext operationContext) { IgniteCache<Object, BinaryObject> entityCache = provider.getEntityCache( key.getMetadata() ); Object id = provider.createKeyObject( key ); BinaryObject bo = entityCache.get( id ); if ( bo != null ) { return new Tuple( new IgniteTupleSnapshot( id, bo, key.getMetadata() ), SnapshotType.UPDATE ); } else { return null; } } @Override public List<Tuple> getTuples(EntityKey[] keys, TupleContext tupleContext) { List<Tuple> result = new ArrayList<>( keys.length ); IgniteCache<Object, BinaryObject> entityCache = provider.getEntityCache( keys[0].getMetadata() ); Map<EntityKey, Object> ids = new HashMap<>( keys.length ); for ( EntityKey key : keys ) { ids.put( key, provider.createKeyObject( key ) ); } Map<Object, BinaryObject> objects = entityCache.getAll( new HashSet<>( ids.values() ) ); for ( EntityKey key : keys ) { Object id = ids.get( key ); BinaryObject bo = objects.get( id ); result.add( bo != null ? new Tuple( new IgniteTupleSnapshot( id, bo, key.getMetadata() ), SnapshotType.UPDATE ) : null ); } return result; } @Override public Tuple createTuple(EntityKey key, OperationContext operationContext) { IgniteCache<Object, BinaryObject> entityCache = provider.getEntityCache( key.getMetadata() ); if ( entityCache == null ) { throw log.cacheNotFound( key.getMetadata().getTable() ); } Object id = provider.createKeyObject( key ); return new Tuple( new IgniteTupleSnapshot( id, null, key.getMetadata() ), SnapshotType.INSERT ); } @Override public void insertOrUpdateTuple(EntityKey key, TuplePointer tuplePointer, TupleContext tupleContext) throws TupleAlreadyExistsException { IgniteCache<Object, BinaryObject> entityCache = provider.getEntityCache( key.getMetadata() ); Tuple tuple = tuplePointer.getTuple(); Object keyObject = null; BinaryObjectBuilder builder = null; IgniteTupleSnapshot tupleSnapshot = (IgniteTupleSnapshot) tuple.getSnapshot(); keyObject = tupleSnapshot.getCacheKey(); if ( tuple.getSnapshotType() == SnapshotType.UPDATE ) { builder = provider.createBinaryObjectBuilder( tupleSnapshot.getCacheValue() ); } else { builder = provider.createBinaryObjectBuilder( provider.getEntityTypeName( key.getMetadata().getTable() ) ); } for ( String columnName : tuple.getColumnNames() ) { Object value = tuple.get( columnName ); if ( value != null ) { builder.setField( StringHelper.realColumnName( columnName ), value ); } else { builder.removeField( StringHelper.realColumnName( columnName ) ); } } BinaryObject valueObject = builder.build(); entityCache.put( keyObject, valueObject ); tuplePointer.setTuple( new Tuple( new IgniteTupleSnapshot( keyObject, valueObject, key.getMetadata() ), SnapshotType.UPDATE ) ); } @Override public void removeTuple(EntityKey key, TupleContext tupleContext) { IgniteCache<Object, BinaryObject> entityCache = provider.getEntityCache( key.getMetadata() ); entityCache.remove( provider.createKeyObject( key ) ); } @Override public Association getAssociation(AssociationKey key, AssociationContext associationContext) { Association result = null; IgniteCache<Object, BinaryObject> associationCache = provider.getAssociationCache( key.getMetadata() ); if ( associationCache == null ) { throw log.cacheNotFound( key.getMetadata().getTable() ); } if ( key.getMetadata().getAssociationKind() == AssociationKind.ASSOCIATION ) { QueryHints.Builder hintsBuilder = new QueryHints.Builder(); Boolean isCollocated = associationContext.getAssociationTypeContext().getOptionsContext().getUnique( CollocatedAssociationOption.class ); if ( isCollocated ) { hintsBuilder.setAffinityRun( true ); hintsBuilder.setAffinityKey( provider.createParentKeyObject( key ) ); } QueryHints hints = hintsBuilder.build(); SqlFieldsQuery sqlQuery = provider.createSqlFieldsQueryWithLog( createAssociationQuery( key, true ), hints, key.getColumnValues() ); Iterable<List<?>> list = executeWithHints( associationCache, sqlQuery, hints ); Iterator<List<?>> iterator = list.iterator(); if ( iterator.hasNext() ) { Map<Object, BinaryObject> associationMap = new HashMap<>(); while ( iterator.hasNext() ) { List<?> item = iterator.next(); Object id = item.get( 0 ); BinaryObject bo = (BinaryObject) item.get( 1 ); associationMap.put( id, bo ); } result = new Association( new IgniteAssociationSnapshot( key, associationMap ) ); } } else if ( key.getMetadata().getAssociationKind() == AssociationKind.EMBEDDED_COLLECTION ) { result = new Association( new IgniteEmbeddedAssociationSnapshot( key, associationContext.getEntityTuplePointer().getTuple() ) ); } else { throw new UnsupportedOperationException( "Unknown association kind " + key.getMetadata().getAssociationKind() ); } return result; } private String createAssociationQuery(AssociationKey key, boolean selectObjects) { StringBuilder sb = new StringBuilder(); if ( selectObjects ) { sb.append( "SELECT _KEY, _VAL FROM " ); } else { sb.append( "SELECT _KEY FROM " ); } sb.append( key.getMetadata().getTable() ).append( " WHERE " ); boolean first = true; for ( String columnName : key.getColumnNames() ) { if ( !first ) { sb.append( " AND " ); } else { first = false; } sb.append( StringHelper.realColumnName( columnName ) ).append( "=?" ); } return sb.toString(); } @Override public Association createAssociation(AssociationKey key, AssociationContext associationContext) { if ( key.getMetadata().getAssociationKind() == AssociationKind.ASSOCIATION ) { return new Association( new IgniteAssociationSnapshot( key ) ); } else if ( key.getMetadata().getAssociationKind() == AssociationKind.EMBEDDED_COLLECTION ) { return new Association( new IgniteEmbeddedAssociationSnapshot( key, associationContext.getEntityTuplePointer().getTuple() ) ); } else { throw new UnsupportedOperationException( "Unknown association kind " + key.getMetadata().getAssociationKind() ); } } @Override public void insertOrUpdateAssociation(AssociationKey key, Association association, AssociationContext associationContext) { if ( key.getMetadata().isInverse() ) { return; } IgniteCache<Object, BinaryObject> associationCache = provider.getAssociationCache( key.getMetadata() ); if ( key.getMetadata().getAssociationKind() == AssociationKind.ASSOCIATION ) { Map<Object, BinaryObject> changedObjects = new HashMap<>(); Set<Object> removedObjects = new HashSet<>(); boolean thirdTableAssociation = IgniteAssociationSnapshot.isThirdTableAssociation( key.getMetadata() ); for ( AssociationOperation op : association.getOperations() ) { AssociationSnapshot snapshot = association.getSnapshot(); Tuple previousStateTuple = snapshot.get( op.getKey() ); Tuple currentStateTuple = op.getValue(); Object previousId = previousStateTuple != null ? ( (IgniteAssociationRowSnapshot) previousStateTuple.getSnapshot() ).getCacheKey() : null; if ( op.getType() == AssociationOperationType.CLEAR || op.getType() == AssociationOperationType.REMOVE && !thirdTableAssociation ) { BinaryObject clearBo = associationCache.get( previousId ); if ( clearBo != null ) { BinaryObjectBuilder clearBoBuilder = provider.createBinaryObjectBuilder( clearBo ); for ( String columnName : key.getColumnNames() ) { clearBoBuilder.removeField( columnName ); } for ( String columnName : key.getMetadata().getRowKeyIndexColumnNames() ) { clearBoBuilder.removeField( columnName ); } changedObjects.put( previousId, clearBoBuilder.build() ); } } else if ( op.getType() == AssociationOperationType.PUT ) { Object currentId = null; if ( currentStateTuple.getSnapshot().isEmpty() ) { currentId = provider.createAssociationKeyObject( op.getKey(), key.getMetadata() ); } else { currentId = ( (IgniteAssociationRowSnapshot) currentStateTuple.getSnapshot() ).getCacheKey(); } BinaryObject putBo = previousId != null ? associationCache.get( previousId ) : null; BinaryObjectBuilder putBoBuilder = null; if ( putBo != null ) { boolean hasChanges = false; for ( String columnName : currentStateTuple.getColumnNames() ) { if ( key.getMetadata().getAssociatedEntityKeyMetadata().getEntityKeyMetadata().isKeyColumn( columnName ) ) { continue; } hasChanges = Objects.equals( currentStateTuple.get( columnName ), putBo.field( columnName ) ); if ( hasChanges ) { break; } } if ( !hasChanges ) { //vk: all changes already set. nothing to update continue; } putBoBuilder = provider.createBinaryObjectBuilder( putBo ); } else { putBoBuilder = provider.createBinaryObjectBuilder( provider.getEntityTypeName( key.getMetadata().getTable() ) ); } for ( String columnName : currentStateTuple.getColumnNames() ) { if ( key.getMetadata().getAssociatedEntityKeyMetadata().getEntityKeyMetadata().isKeyColumn( columnName ) ) { continue; } Object value = currentStateTuple.get( columnName ); if ( value != null ) { putBoBuilder.setField( StringHelper.realColumnName( columnName ), value ); } else { putBoBuilder.removeField( columnName ); } } if ( previousId != null && !previousId.equals( currentId ) ) { removedObjects.add( previousId ); } changedObjects.put( currentId, putBoBuilder.build() ); } else if ( op.getType() == AssociationOperationType.REMOVE ) { removedObjects.add( previousId ); } else { throw new UnsupportedOperationException( "AssociationOperation not supported: " + op.getType() ); } } if ( !changedObjects.isEmpty() ) { associationCache.putAll( changedObjects ); } if ( !removedObjects.isEmpty() ) { associationCache.removeAll( removedObjects ); } } else if ( key.getMetadata().getAssociationKind() == AssociationKind.EMBEDDED_COLLECTION ) { String indexColumnName = findIndexColumnName( key.getMetadata() ); boolean searchByValue = indexColumnName == null; Object id = ( (IgniteTupleSnapshot) associationContext.getEntityTuplePointer().getTuple().getSnapshot() ).getCacheKey(); BinaryObject binaryObject = associationCache.get( id ); Contracts.assertNotNull( binaryObject, "binaryObject" ); String column = StringHelper.realColumnName( key.getMetadata().getCollectionRole() ); Object binaryObjects[] = binaryObject.field( column ); List<BinaryObject> associationObjects = new ArrayList<>(); if ( binaryObjects != null ) { for ( int i = 0; i < binaryObjects.length; i++ ) { associationObjects.add( (BinaryObject) binaryObjects[i] ); } } EntityKeyMetadata itemMetadata = key.getMetadata().getAssociatedEntityKeyMetadata().getEntityKeyMetadata(); for ( AssociationOperation op : association.getOperations() ) { int index = findIndexByRowKey( associationObjects, op.getKey(), indexColumnName ); switch ( op.getType() ) { case PUT: Tuple currentStateTuple = op.getValue(); BinaryObjectBuilder putBoBuilder = provider.createBinaryObjectBuilder( provider.getEntityTypeName( itemMetadata.getTable() ) ); for ( String columnName : op.getKey().getColumnNames() ) { Object value = op.getKey().getColumnValue( columnName ); if ( value != null ) { putBoBuilder.setField( StringHelper.stringAfterPoint( columnName ), value ); } } for ( String columnName : itemMetadata.getColumnNames() ) { Object value = currentStateTuple.get( columnName ); if ( value != null ) { putBoBuilder.setField( StringHelper.stringAfterPoint( columnName ), value ); } } BinaryObject itemObject = putBoBuilder.build(); if ( index >= 0 ) { associationObjects.set( index, itemObject ); } else { associationObjects.add( itemObject ); } break; case REMOVE: if ( index >= 0 ) { associationObjects.remove( index ); } break; default: throw new HibernateException( "AssociationOperation not supported: " + op.getType() ); } } BinaryObjectBuilder binaryObjectBuilder = provider.createBinaryObjectBuilder( binaryObject ); binaryObjectBuilder.setField( column, associationObjects.toArray( new BinaryObject[ associationObjects.size() ] ) ); binaryObject = binaryObjectBuilder.build(); associationCache.put( id, binaryObject ); } } private int findIndexByRowKey(List<BinaryObject> objects, RowKey rowKey, String indexColumnName) { int result = -1; if ( !objects.isEmpty() ) { String columnNames[] = indexColumnName == null ? rowKey.getColumnNames() : new String[] { indexColumnName }; String fieldNames[] = new String[columnNames.length]; for ( int i = 0; i < columnNames.length; i++ ) { fieldNames[i] = StringHelper.stringAfterPoint( columnNames[i] ); } for ( int i = 0; i < objects.size() && result < 0; i++ ) { BinaryObject bo = objects.get( i ); boolean thisIsIt = true; for ( int j = 0; j < columnNames.length; j++ ) { if ( !Objects.equals( rowKey.getColumnValue( columnNames[j] ), bo.field( fieldNames[j] ) ) ) { thisIsIt = false; break; } } if ( thisIsIt ) { result = i; } } } return result; } /** * @param associationMetadata * @return index column name for indexed embedded collections or null for collections without index */ private String findIndexColumnName(AssociationKeyMetadata associationMetadata) { String indexColumnName = null; if ( associationMetadata.getAssociationType() == AssociationType.SET || associationMetadata.getAssociationType() == AssociationType.BAG ) { // String cols[] = associationMetadata.getColumnsWithoutKeyColumns( // Arrays.asList( associationMetadata.getRowKeyColumnNames() ) // ); } else { if ( associationMetadata.getRowKeyIndexColumnNames().length > 1 ) { throw new UnsupportedOperationException( "Multiple index columns not implemented yet" ); } indexColumnName = associationMetadata.getRowKeyIndexColumnNames()[0]; } return indexColumnName; } @Override public void removeAssociation(AssociationKey key, AssociationContext associationContext) { if ( key.getMetadata().isInverse() ) { return; } IgniteCache<Object, BinaryObject> associationCache = provider.getAssociationCache( key.getMetadata() ); if ( key.getMetadata().getAssociationKind() == AssociationKind.ASSOCIATION ) { QueryHints.Builder hintsBuilder = new QueryHints.Builder(); Boolean isCollocated = associationContext.getAssociationTypeContext().getOptionsContext().getUnique( CollocatedAssociationOption.class ); if ( isCollocated ) { throw new NotYetImplementedException(); // hintsBuilder.setAffinityRun( true ); // hintsBuilder.setAffinityKey( provider.createKeyObject( key ) ); } QueryHints hints = hintsBuilder.build(); if ( !IgniteAssociationSnapshot.isThirdTableAssociation( key.getMetadata() ) ) { // clear reference Map<Object, BinaryObject> changedObjects = new HashMap<>(); SqlFieldsQuery sqlQuery = provider.createSqlFieldsQueryWithLog( createAssociationQuery( key, true ), hints, key.getColumnValues() ); Iterable<List<?>> list = executeWithHints( associationCache, sqlQuery, hints ); for ( List<?> item : list ) { Object id = item.get( /* _KEY */ 0 ); BinaryObject clearBo = (BinaryObject) item.get( /* _VALUE */ 1 ); if ( clearBo != null ) { BinaryObjectBuilder clearBoBuilder = provider.createBinaryObjectBuilder( clearBo ); for ( String columnName : key.getMetadata().getRowKeyColumnNames() ) { clearBoBuilder.removeField( StringHelper.realColumnName( columnName ) ); } changedObjects.put( id, clearBoBuilder.build() ); } } if ( !changedObjects.isEmpty() ) { associationCache.putAll( changedObjects ); } } else { // remove objects Set<Object> removedObjects = new HashSet<>(); SqlFieldsQuery sqlQuery = provider.createSqlFieldsQueryWithLog( createAssociationQuery( key, false ), hints, key.getColumnValues() ); Iterable<List<?>> list = executeWithHints( associationCache, sqlQuery, hints ); for ( List<?> item : list ) { removedObjects.add( /* _KEY */ item.get( 0 ) ); } if ( !removedObjects.isEmpty() ) { associationCache.removeAll( removedObjects ); } } } else if ( key.getMetadata().getAssociationKind() == AssociationKind.EMBEDDED_COLLECTION ) { Object id = ( (IgniteTupleSnapshot) associationContext.getEntityTuplePointer().getTuple().getSnapshot() ).getCacheKey(); BinaryObject binaryObject = associationCache.get( id ); Contracts.assertNotNull( binaryObject, "binaryObject" ); BinaryObjectBuilder binaryObjectBuilder = provider.createBinaryObjectBuilder( binaryObject ); binaryObjectBuilder.removeField( key.getMetadata().getCollectionRole() ); binaryObject = binaryObjectBuilder.build(); associationCache.put( id, binaryObject ); } } @Override public boolean isStoredInEntityStructure(AssociationKeyMetadata associationKeyMetadata, AssociationTypeContext associationTypeContext) { return false; } @Override public Number nextValue(NextValueRequest request) { Long result = null; switch ( request.getKey().getMetadata().getType() ) { case TABLE: IgniteCache<String, Long> cache = provider.getIdSourceCache( request.getKey().getMetadata() ); String idSourceKey = request.getKey().getColumnValue(); Long previousValue = cache.get( idSourceKey ); if ( previousValue == null ) { result = (long) request.getInitialValue(); if ( !cache.putIfAbsent( idSourceKey, result ) ) { previousValue = (long) request.getInitialValue(); } } if ( previousValue != null ) { while ( true ) { result = previousValue + request.getIncrement(); if ( cache.replace( idSourceKey, previousValue, result ) ) { break; } else { previousValue = cache.get( idSourceKey ); } } } break; case SEQUENCE: IgniteAtomicSequence seq = provider.atomicSequence( request.getKey().getMetadata().getName(), request.getInitialValue(), false ); result = seq.getAndAdd( request.getIncrement() ); break; } return result; } @Override public boolean supportsSequences() { return true; } @Override public void forEachTuple(ModelConsumer consumer, TupleTypeContext tupleTypeContext, EntityKeyMetadata entityKeyMetadata) { throw new UnsupportedOperationException( "forEachTuple() is not implemented" ); } @Override public int executeBackendUpdateQuery(BackendQuery<IgniteQueryDescriptor> query, QueryParameters queryParameters, TupleContext tupleContext) { throw new UnsupportedOperationException( "executeBackendUpdateQuery() is not implemented" ); } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public ClosableIterator<Tuple> executeBackendQuery(BackendQuery<IgniteQueryDescriptor> backendQuery, QueryParameters queryParameters, TupleContext tupleContext) { Integer firstRow = queryParameters.getRowSelection().getFirstRow(); if ( firstRow != null && firstRow.intValue() < 0 ) { throw new IllegalArgumentException( "Query argument firstResult cannot be negative" ); } EntityKeyMetadata selectionEntity; if ( backendQuery.getQuery().getRootKeyMetadata() == null ) { if ( backendQuery.getSingleEntityMetadataInformationOrNull() != null ) { selectionEntity = backendQuery.getSingleEntityMetadataInformationOrNull().getEntityKeyMetadata(); } else { throw new IllegalArgumentException( "Cannot determine any selection entity" ); } } else { selectionEntity = backendQuery.getQuery().getRootKeyMetadata(); } IgniteCache<Object, BinaryObject> cache = provider.getEntityCache( selectionEntity ); List<Object> parameterValues; if ( !queryParameters.getPositionalParameters().isEmpty() ) { if ( !queryParameters.getNamedParameters().isEmpty() ) { throw new IllegalArgumentException( "Mixing positional and named parameters" ); } parameterValues = new ArrayList<>( queryParameters.getPositionalParameters().size() ); for ( TypedGridValue typedGridValue : queryParameters.getPositionalParameters() ) { parameterValues.add( typedGridValue.getValue() ); } } else { parameterValues = backendQuery.getQuery().getIndexedParameters(); } QueryHints hints = ( new QueryHints.Builder( null /* queryParameters.getQueryHints() */ ) ).build(); SqlFieldsQuery sqlQuery = provider.createSqlFieldsQueryWithLog( backendQuery.getQuery().getSql(), hints, parameterValues.toArray() ); Iterable<List<?>> result = executeWithHints( cache, sqlQuery, hints ); if ( backendQuery.getQuery().hasScalar() ) { return new ProjectionResultCursor( result, backendQuery.getQuery().getQueryReturns(), queryParameters.getRowSelection() ); } else { return new SingleEntityResultCursor( result, queryParameters.getRowSelection(), selectionEntity ); } } private Iterable<List<?>> executeWithHints(IgniteCache<Object, BinaryObject> cache, SqlFieldsQuery sqlQuery, QueryHints hints) { Iterable<List<?>> result; if ( hints.isLocal() ) { if ( !provider.isClientMode() ) { sqlQuery.setLocal( true ); } } if ( hints.isAffinityRun() ) { result = provider.affinityCall( cache.getName(), hints.getAffinityKey(), sqlQuery ); } else { result = cache.query( sqlQuery ); } return result; } @Override public ParameterMetadataBuilder getParameterMetadataBuilder() { return IgniteParameterMetadataBuilder.INSTANCE; } @Override public IgniteQueryDescriptor parseNativeQuery(String nativeQuery) { IgniteSqlQueryParser parser = new IgniteSqlQueryParser( nativeQuery ); return parser.buildQueryDescriptor(); } @Override public GridType overrideType(Type type) { return IgniteGridTypeMapper.INSTANCE.overrideType( type ); } private abstract class BaseResultCursor<T> implements ClosableIterator<Tuple> { private final Iterator<T> resultIterator; private final Integer maxRows; private int rowNum = 0; public BaseResultCursor(Iterable<T> resultCursor, RowSelection rowSelection) { this.resultIterator = resultCursor.iterator(); this.maxRows = rowSelection.getMaxRows(); iterateToFirst( rowSelection ); } private void iterateToFirst(RowSelection rowSelection) { int firstRow = rowSelection.getFirstRow() != null ? rowSelection.getFirstRow() : 0; for ( int i = 0; i < firstRow && resultIterator.hasNext(); i++ ) { resultIterator.next(); } } @Override public boolean hasNext() { return ( maxRows == null || rowNum < maxRows ) && resultIterator.hasNext(); } @Override public Tuple next() { T value = resultIterator.next(); rowNum++; return new Tuple( createTupleSnapshot( value ), SnapshotType.UPDATE ); } abstract TupleSnapshot createTupleSnapshot(T value); @Override public void remove() { resultIterator.remove(); } @Override public void close() { } } private class ProjectionResultCursor extends BaseResultCursor<List<?>> { private final List<ScalarReturn> queryReturns; ProjectionResultCursor(Iterable<List<?>> resultCursor, List<ScalarReturn> queryReturns, RowSelection rowSelection) { super( resultCursor, rowSelection ); this.queryReturns = queryReturns; } @Override TupleSnapshot createTupleSnapshot(List<?> value) { Map<String, Object> map = new HashMap<>(); for ( int i = 0; i < value.size(); i++ ) { map.put( queryReturns.get( i ).getColumnAlias(), value.get( i ) ); } return new MapTupleSnapshot( map ); } } private class SingleEntityResultCursor extends BaseResultCursor<List<?>> { private final EntityKeyMetadata keyMetadata; SingleEntityResultCursor(Iterable<List<?>> resultCursor, RowSelection rowSelection, EntityKeyMetadata keyMetadata) { super( resultCursor, rowSelection ); this.keyMetadata = keyMetadata; } @Override TupleSnapshot createTupleSnapshot(List<?> value) { return new IgniteTupleSnapshot( /* _KEY */ value.get( 0 ), /* _VAL */ (BinaryObject) value.get( 1 ), keyMetadata ); } } }