/* * ToroDB * Copyright © 2014 8Kdata Technology (www.8kdata.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.torodb.mongodb.repl.impl; import static com.eightkdata.mongowp.bson.utils.DefaultBsonValues.newInt; import com.eightkdata.mongowp.OpTime; import com.eightkdata.mongowp.bson.BsonDocument; import com.eightkdata.mongowp.bson.utils.DefaultBsonValues; import com.eightkdata.mongowp.client.core.MongoConnection; import com.eightkdata.mongowp.exceptions.BadValueException; import com.eightkdata.mongowp.exceptions.MongoException; import com.eightkdata.mongowp.exceptions.NoSuchKeyException; import com.eightkdata.mongowp.exceptions.OplogOperationUnsupported; import com.eightkdata.mongowp.exceptions.OplogStartMissingException; import com.eightkdata.mongowp.exceptions.TypesMismatchException; import com.eightkdata.mongowp.messages.request.QueryMessage.QueryOption; import com.eightkdata.mongowp.messages.request.QueryMessage.QueryOptions; import com.eightkdata.mongowp.server.api.oplog.OplogOperation; import com.eightkdata.mongowp.server.api.pojos.MongoCursor; import com.eightkdata.mongowp.server.api.pojos.MongoCursor.Batch; import com.eightkdata.mongowp.server.api.pojos.MongoCursor.DeadCursorException; import com.eightkdata.mongowp.server.api.pojos.TransformationMongoCursor; import com.eightkdata.mongowp.utils.BsonArrayBuilder; import com.google.common.base.Preconditions; import com.google.common.net.HostAndPort; import com.torodb.mongodb.commands.pojos.OplogOperationParser; import com.torodb.mongodb.repl.OplogReader; import java.util.EnumSet; import java.util.function.Consumer; public abstract class AbstractMongoOplogReader implements OplogReader { private static final String DATABASE = "local"; private static final String COLLECTION = "oplog.rs"; private static final BsonDocument NATURAL_ORDER_SORT = DefaultBsonValues.newDocument("$natural", newInt(1)); private static final BsonDocument INVERSE_ORDER_SORT = DefaultBsonValues.newDocument("$natural", newInt(-1)); protected abstract MongoConnection consumeConnection(); protected abstract void releaseConnection(MongoConnection connection); @Override public MongoCursor<OplogOperation> queryGte(OpTime lastFetchedOpTime) throws MongoException { BsonDocument query = DefaultBsonValues.newDocument( "ts", DefaultBsonValues.newDocument("$gte", lastFetchedOpTime.getTimestamp()) ); EnumSet<QueryOption> flags = EnumSet.of( QueryOption.AWAIT_DATA, QueryOption.TAILABLE_CURSOR ); return query(query, flags, NATURAL_ORDER_SORT); } @Override public OplogOperation getLastOp() throws OplogStartMissingException, OplogOperationUnsupported, MongoException { return getFirstOrLastOp(false); } @Override public OplogOperation getFirstOp() throws OplogStartMissingException, OplogOperationUnsupported, MongoException { return getFirstOrLastOp(true); } @Override public MongoCursor<OplogOperation> between( OpTime from, boolean includeFrom, OpTime to, boolean includeTo) throws MongoException { BsonArrayBuilder conditions = new BsonArrayBuilder(); conditions.add( DefaultBsonValues.newDocument( "ts", DefaultBsonValues.newDocument(includeFrom ? "$gte" : "$gt", from.getTimestamp()) ) ); conditions.add( DefaultBsonValues.newDocument( "ts", DefaultBsonValues.newDocument(includeTo ? "$lte" : "$lt", to.getTimestamp()) ) ); EnumSet<QueryOption> flags = EnumSet.noneOf(QueryOption.class); return query( DefaultBsonValues.newDocument("$and", conditions.build()), flags, NATURAL_ORDER_SORT); } public MongoCursor<OplogOperation> query(BsonDocument query, EnumSet<QueryOption> flags, BsonDocument sortBy) throws MongoException { Preconditions.checkState(!isClosed(), "You have to connect this client before"); MongoConnection connection = consumeConnection(); MongoCursor<BsonDocument> cursor = connection.query( DATABASE, COLLECTION, query, 0, 0, new QueryOptions(flags), sortBy, null ); return new MyCursor<>( connection, TransformationMongoCursor.create( cursor, OplogOperationParser.asFunction() ) ); } private OplogOperation getFirstOrLastOp(boolean first) throws OplogStartMissingException, OplogOperationUnsupported, MongoException { Preconditions.checkState(!isClosed(), "You have to connect this client before"); BsonDocument query = DefaultBsonValues.EMPTY_DOC; BsonDocument orderBy = first ? NATURAL_ORDER_SORT : INVERSE_ORDER_SORT; EnumSet<QueryOption> flags = EnumSet.of(QueryOption.SLAVE_OK); BsonDocument doc; MongoConnection connection = consumeConnection(); try { MongoCursor<BsonDocument> cursor = connection.query( DATABASE, COLLECTION, query, 0, 1, new QueryOptions(flags), orderBy, null ); try { Batch<BsonDocument> batch = cursor.fetchBatch(); try { if (!batch.hasNext()) { throw new OplogStartMissingException(getSyncSource()); } doc = batch.next(); } finally { batch.close(); } } finally { cursor.close(); } try { return OplogOperationParser.fromBson(doc); } catch (BadValueException | TypesMismatchException | NoSuchKeyException ex) { throw new OplogOperationUnsupported(doc, ex); } } finally { releaseConnection(connection); } } private class MyCursor<T> implements MongoCursor<T> { private final MongoConnection connection; private final MongoCursor<T> delegate; private MyCursor(MongoConnection connection, MongoCursor<T> delegate) { this.connection = connection; this.delegate = delegate; } @Override public String getDatabase() { return delegate.getDatabase(); } @Override public String getCollection() { return delegate.getCollection(); } @Override public long getId() { return delegate.getId(); } @Override public void setMaxBatchSize(int newBatchSize) { delegate.setMaxBatchSize(newBatchSize); } @Override public int getMaxBatchSize() { return delegate.getMaxBatchSize(); } @Override public boolean isTailable() { return delegate.isTailable(); } @Override public Batch<T> fetchBatch() throws MongoException, DeadCursorException { return delegate.fetchBatch(); } @Override public T next() { return delegate.next(); } @Override public HostAndPort getServerAddress() { return delegate.getServerAddress(); } @Override public boolean hasNext() { return delegate.hasNext(); } @Override public T tryNext() { return delegate.tryNext(); } @Override public void remove() { delegate.remove(); } @Override public void forEachRemaining(Consumer<? super T> action) { delegate.forEachRemaining(action); } @Override public Batch<T> tryFetchBatch() throws MongoException, DeadCursorException { return delegate.tryFetchBatch(); } @Override public boolean isClosed() { return delegate.isClosed(); } @Override public void close() { delegate.close(); releaseConnection(connection); } } }