/* * 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.commands.signatures.general; import com.eightkdata.mongowp.ErrorCode; import com.eightkdata.mongowp.MongoConstants; import com.eightkdata.mongowp.OpTime; import com.eightkdata.mongowp.WriteConcern; import com.eightkdata.mongowp.WriteConcern.SyncMode; import com.eightkdata.mongowp.bson.BsonDocument; import com.eightkdata.mongowp.bson.BsonDocument.Entry; import com.eightkdata.mongowp.bson.BsonObjectId; import com.eightkdata.mongowp.bson.BsonTimestamp; import com.eightkdata.mongowp.exceptions.BadValueException; import com.eightkdata.mongowp.exceptions.FailedToParseException; import com.eightkdata.mongowp.exceptions.NoSuchKeyException; import com.eightkdata.mongowp.exceptions.TypesMismatchException; import com.eightkdata.mongowp.fields.ArrayField; import com.eightkdata.mongowp.fields.BooleanField; import com.eightkdata.mongowp.fields.DocField; import com.eightkdata.mongowp.fields.DoubleField; import com.eightkdata.mongowp.fields.IntField; import com.eightkdata.mongowp.fields.ObjectIdField; import com.eightkdata.mongowp.fields.StringField; import com.eightkdata.mongowp.fields.TimestampField; import com.eightkdata.mongowp.server.api.impl.AbstractNotAliasableCommand; import com.eightkdata.mongowp.server.callback.WriteOpResult; import com.eightkdata.mongowp.utils.BsonArrayBuilder; import com.eightkdata.mongowp.utils.BsonDocumentBuilder; import com.eightkdata.mongowp.utils.BsonReaderTool; import com.google.common.collect.ImmutableList; import com.google.common.net.HostAndPort; import com.torodb.mongodb.commands.signatures.general.GetLastErrorCommand.GetLastErrorArgument; import com.torodb.mongodb.commands.signatures.general.GetLastErrorCommand.GetLastErrorReply; import javax.annotation.Nonnegative; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; public class GetLastErrorCommand extends AbstractNotAliasableCommand<GetLastErrorArgument, GetLastErrorReply> { public static final GetLastErrorCommand INSTANCE = new GetLastErrorCommand(); private GetLastErrorCommand() { super("getLastError"); } @Override public Class<? extends GetLastErrorArgument> getArgClass() { return GetLastErrorArgument.class; } @Override public GetLastErrorArgument unmarshallArg(BsonDocument requestDoc) throws TypesMismatchException, NoSuchKeyException, BadValueException { return GetLastErrorArgument.unmarshall(requestDoc); } @Override public BsonDocument marshallArg(GetLastErrorArgument request) { throw new UnsupportedOperationException("Not supported yet."); //TODO } @Override public Class<? extends GetLastErrorReply> getResultClass() { return GetLastErrorReply.class; } @Override public BsonDocument marshallResult(GetLastErrorReply reply) { return reply.marshall(); } @Override public GetLastErrorReply unmarshallResult(BsonDocument resultDoc) { throw new UnsupportedOperationException("Not supported yet."); //TODO } @SuppressWarnings("checkstyle:AbbreviationAsWordInName") public static class GetLastErrorArgument { private static final TimestampField W_OP_TIME_FIELD = new TimestampField("wOpTime"); private static final ObjectIdField W_ELECTION_ID_FIELD = new ObjectIdField("wElectionId"); @Nullable private final WriteConcern writeConcern; @Nullable private final BsonDocument badGLE; @Nullable private final OpTime wOpTime; @Nullable private final BsonObjectId wElectionId; @Nullable private final String badGLEMessage; private final ErrorCode badGLEErrorCode; public GetLastErrorArgument( WriteConcern writeConcern, BsonDocument badGLE, String badGLEMessage, @Nonnull ErrorCode badGLEErrorCode, OpTime wOpTime, BsonObjectId wElectionId) { this.writeConcern = writeConcern; this.badGLE = badGLE; this.badGLEMessage = badGLEMessage; this.badGLEErrorCode = badGLEErrorCode; this.wOpTime = wOpTime; this.wElectionId = wElectionId; } public boolean isValid() { return badGLE == null; } public String getBadGLEMessage() { return badGLEMessage; } @Nonnull public ErrorCode getBadGLEErrorCode() { return badGLEErrorCode; } @Nullable public WriteConcern getWriteConcern() { return writeConcern; } @Nullable public BsonDocument getBadGLE() { return badGLE; } @Nullable public OpTime getWOpTime() { return wOpTime; } @Nullable public BsonObjectId getWElectionId() { return wElectionId; } private static GetLastErrorArgument unmarshall(BsonDocument requestDoc) { OpTime opTime; try { BsonTimestamp ts = BsonReaderTool.getTimestamp( requestDoc, W_OP_TIME_FIELD); opTime = new OpTime(ts); } catch (TypesMismatchException ex) { return new GetLastErrorArgument( null, requestDoc, ex.getLocalizedMessage(), ex.getErrorCode(), null, null ); } catch (NoSuchKeyException ex) { opTime = null; } BsonObjectId electionId; try { electionId = BsonReaderTool.getObjectId(requestDoc, W_ELECTION_ID_FIELD, null); } catch (TypesMismatchException ex) { return new GetLastErrorArgument( null, requestDoc, ex.getLocalizedMessage(), ex.getErrorCode(), opTime, null ); } WriteConcern wc; try { wc = WriteConcern.fromDocument(requestDoc); } catch (FailedToParseException ex) { return new GetLastErrorArgument( null, requestDoc, ex.getLocalizedMessage(), ex.getErrorCode(), opTime, electionId ); } return new GetLastErrorArgument( wc, null, null, ErrorCode.OK, opTime, electionId ); } } public static class GetLastErrorReply { private static final DoubleField OK_FIELD = new DoubleField("ok"); private static final DoubleField CONNECTION_ID_FIELD = new DoubleField("connectionId"); private static final DocField BAD_GLE_FIELD = new DocField("badGLE"); private static final StringField ERR_MSG_FIELD = new StringField("errmsg"); private static final StringField ERR_FIELD = new StringField("err"); private static final IntField CODE_FIELD = new IntField("code"); @Nonnull private final ErrorCode thisError; @Nullable private final String thisErrorMessage; private final long connectionId; @Nullable private final WriteOpResult writeOpResult; @Nullable private final GetLastErrorArgument arg; @Nullable private final WriteConcernEnforcementResult wcer; public GetLastErrorReply( @Nonnull ErrorCode thisError, @Nullable String thisErrorMessage, long connectionId, @Nullable WriteOpResult writeOpResult, @Nonnull GetLastErrorArgument arg, @Nullable WriteConcernEnforcementResult wcer) { this.thisError = thisError; this.thisErrorMessage = thisErrorMessage; this.connectionId = connectionId; this.writeOpResult = writeOpResult; this.arg = arg; this.wcer = wcer; } private BsonDocument marshall() { BsonDocumentBuilder builder = new BsonDocumentBuilder(); builder.append(CONNECTION_ID_FIELD, connectionId); if (arg.getBadGLE() != null) { builder.append(BAD_GLE_FIELD, arg.getBadGLE()); builder.append(ERR_MSG_FIELD, arg.getBadGLEMessage()); return builder.build(); } if (writeOpResult != null) { for (Entry<?> entry : writeOpResult.marshall()) { builder.appendUnsafe(entry.getKey(), entry.getValue()); } if (writeOpResult.errorOcurred()) { return builder.build(); } } if (wcer != null) { wcer.marshall(builder); } if (!thisError.equals(ErrorCode.OK)) { builder.append(CODE_FIELD, thisError.getErrorCode()); if (thisErrorMessage != null) { builder.append(ERR_FIELD, thisErrorMessage); } } boolean ok = thisError.equals(ErrorCode.OK); builder.append(OK_FIELD, ok ? MongoConstants.OK : MongoConstants.KO); return builder.build(); } } @Immutable public static class WriteConcernEnforcementResult { private static final IntField SYNC_MILLIS_FIELD = new IntField("syncMillis"); private static final IntField FSYNC_FILES_FIELD = new IntField("fsyncFiles"); private static final StringField ERR_FIELD = new StringField("err"); private static final IntField W_TIMED_FIELD = new IntField("wtimeout"); private static final IntField WAITED_FIELD = new IntField("waited"); private static final BooleanField W_TIMEDOUT_FIELD = new BooleanField("wtimeout"); private static final ArrayField WRITTEN_TO_FIELD = new ArrayField("writtenTo"); @Nonnull private final WriteConcern writeConcern; @Nullable private final String err; @Nullable @Nonnegative private final Integer syncMillis; @Nullable @Nonnegative private final Integer fsyncFiles; private final boolean wTimedOut; @Nullable @Nonnegative private final Integer wTime; private final ImmutableList<HostAndPort> writtenTo; public WriteConcernEnforcementResult( @Nonnull WriteConcern writeConcern, @Nullable String err, @Nullable Integer syncMillis, @Nullable Integer fsyncFiles, boolean wTimedOut, @Nullable Integer wTime, @Nullable ImmutableList<HostAndPort> writtenTo) { this.writeConcern = writeConcern; this.err = err; this.syncMillis = syncMillis; this.fsyncFiles = fsyncFiles; this.wTimedOut = wTimedOut; this.wTime = wTime; this.writtenTo = writtenTo; } private void marshall(BsonDocumentBuilder builder) { if (syncMillis != null) { builder.append(SYNC_MILLIS_FIELD, syncMillis); } if (fsyncFiles != null) { builder.append(FSYNC_FILES_FIELD, fsyncFiles); } if (wTime != null) { if (wTimedOut) { builder.append(WAITED_FIELD, wTime); } else { builder.append(W_TIMED_FIELD, wTime); } } // *** 2.4 SyncClusterConnection compatibility *** // 2.4 expects either fsync'd files, or a "waited" field exist after running an fsync : true // GLE, but with journaling we don't actually need to run the fsync (fsync command is // preferred in 2.6). So we add a "waited" field if one doesn't exist. if (writeConcern.getSyncMode() == SyncMode.FSYNC && !builder.containsField(WAITED_FIELD) && !builder.containsField(FSYNC_FILES_FIELD)) { if (wTime != null) { builder.append(WAITED_FIELD, wTime); } else { assert syncMillis != null; builder.append(WAITED_FIELD, syncMillis); } } if (wTimedOut) { builder.append(W_TIMEDOUT_FIELD, wTimedOut); } if (writtenTo != null && !writtenTo.isEmpty()) { BsonArrayBuilder array = new BsonArrayBuilder(); for (HostAndPort w : writtenTo) { array.add(w.toString()); } builder.append(WRITTEN_TO_FIELD, array.build()); } else { builder.appendNull(WRITTEN_TO_FIELD); } if (err == null || err.isEmpty()) { builder.appendNull(ERR_FIELD); } else { builder.append(ERR_FIELD, err); } } } }