/* * 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.WriteConcern; import com.eightkdata.mongowp.WriteConcern.SyncMode; import com.eightkdata.mongowp.bson.BsonArray; import com.eightkdata.mongowp.bson.BsonDocument; import com.eightkdata.mongowp.bson.BsonValue; import com.eightkdata.mongowp.bson.annotations.NotMutable; import com.eightkdata.mongowp.bson.utils.DefaultBsonValues; import com.eightkdata.mongowp.exceptions.BadValueException; import com.eightkdata.mongowp.exceptions.FailedToParseException; import com.eightkdata.mongowp.exceptions.MongoException; 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.NumberField; import com.eightkdata.mongowp.fields.StringField; import com.eightkdata.mongowp.server.api.MarshalException; import com.eightkdata.mongowp.server.api.impl.AbstractNotAliasableCommand; import com.eightkdata.mongowp.utils.BsonArrayBuilder; import com.eightkdata.mongowp.utils.BsonDocumentBuilder; import com.eightkdata.mongowp.utils.BsonReaderTool; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.torodb.mongodb.commands.signatures.general.DeleteCommand.DeleteArgument; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; //TODO: Change the command to return writeErrors like InsertCommand public class DeleteCommand extends AbstractNotAliasableCommand<DeleteArgument, Long> { private static final NumberField<?> N_FIELD = new NumberField<>("n"); private static final String COMMAND_NAME = "delete"; public static final DeleteCommand INSTANCE = new DeleteCommand(); private DeleteCommand() { super(COMMAND_NAME); } @Override public Class<? extends DeleteArgument> getArgClass() { return DeleteArgument.class; } @Override public DeleteArgument unmarshallArg(BsonDocument requestDoc) throws BadValueException, TypesMismatchException, NoSuchKeyException, FailedToParseException { return DeleteArgument.unmarshall(requestDoc); } @Override public BsonDocument marshallArg(DeleteArgument request) throws MarshalException { return request.marshall(); } @Override public Class<? extends Long> getResultClass() { return Long.class; } @Override public Long unmarshallResult(BsonDocument resultDoc) throws BadValueException, TypesMismatchException, NoSuchKeyException, FailedToParseException, MongoException { if (resultDoc == null) { return null; } return BsonReaderTool.getNumeric(resultDoc, N_FIELD).asNumber().longValue(); } @Override public BsonDocument marshallResult(Long result) throws MarshalException { if (result == null) { return null; } BsonDocumentBuilder builder = new BsonDocumentBuilder(); builder.appendNumber(N_FIELD, result); return builder.build(); } public static class DeleteArgument { private static final StringField COLLECTION_FIELD = new StringField(COMMAND_NAME); private static final ArrayField STATEMENTS_FIELD = new ArrayField("deletes"); private static final BooleanField ORDERED_FIELD = new BooleanField("ordered"); private static final DocField WRITE_CONCERN_FIELD = new DocField("writeConcern"); private final String collection; private final Iterable<DeleteStatement> statements; private final boolean ordered; private final WriteConcern writeConcern; public DeleteArgument(String collection, Iterable<DeleteStatement> statements, boolean ordered, WriteConcern writeConcern) { this.collection = collection; this.statements = statements; this.ordered = ordered; this.writeConcern = writeConcern; } public String getCollection() { return collection; } public Iterable<DeleteStatement> getStatements() { return statements; } public boolean isOrdered() { return ordered; } public WriteConcern getWriteConcern() { return writeConcern; } public static DeleteArgument unmarshall(BsonDocument doc) throws TypesMismatchException, NoSuchKeyException, FailedToParseException, BadValueException { String collection = BsonReaderTool.getString(doc, COLLECTION_FIELD); boolean ordered = BsonReaderTool.getBoolean(doc, ORDERED_FIELD, true); WriteConcern writeConcern = WriteConcern.fromDocument( BsonReaderTool.getDocument(doc, WRITE_CONCERN_FIELD, null), WriteConcern.with(SyncMode.NONE) ); ImmutableList.Builder<DeleteStatement> deletes = ImmutableList.builder(); BsonArray array = BsonReaderTool.getArray(doc, STATEMENTS_FIELD); for (BsonValue uncastedStatement : array) { if (!uncastedStatement.isDocument()) { throw new BadValueException(STATEMENTS_FIELD.getFieldName() + " array contains the unexpected value '" + uncastedStatement + "' which is not a document"); } deletes.add(DeleteStatement.unmarshall(uncastedStatement.asDocument())); } return new DeleteArgument(collection, deletes.build(), ordered, writeConcern); } public BsonDocument marshall() { BsonArrayBuilder statementsBson = new BsonArrayBuilder(); for (DeleteStatement statement : statements) { statementsBson.add(statement.marshall()); } return new BsonDocumentBuilder() .append(COLLECTION_FIELD, collection) .append(STATEMENTS_FIELD, statementsBson.build()) .append(ORDERED_FIELD, ordered) .append(WRITE_CONCERN_FIELD, writeConcern.toDocument()) .build(); } public static class Builder { private final String collection; private final List<DeleteStatement> statements; private boolean ordered = true; private WriteConcern writeConcern = WriteConcern.fsync(); public Builder(@Nonnull String collection) { this.collection = collection; this.statements = Lists.newArrayList(); } public Builder ordered(boolean ordered) { this.ordered = ordered; return this; } public Builder addStatement(DeleteStatement statement) { this.statements.add(statement); return this; } public Builder writeConcern(WriteConcern wc) { this.writeConcern = wc; return this; } public DeleteArgument build() { Preconditions.checkState(!statements.isEmpty(), "No statement has been provided"); return new DeleteArgument(collection, statements, ordered, writeConcern); } } } public static class DeleteStatement { private static final DocField QUERY_FIELD = new DocField("q"); private static final BooleanField LIMIT_FIELD = new BooleanField("limit"); private final BsonDocument query; private final boolean justOne; public DeleteStatement(@NotMutable @Nullable BsonDocument query, boolean justOne) { this.query = query == null ? DefaultBsonValues.EMPTY_DOC : query; this.justOne = justOne; } public BsonDocument getQuery() { return query; } public boolean isJustOne() { return justOne; } private static DeleteStatement unmarshall(BsonDocument uncastedStatement) throws TypesMismatchException, NoSuchKeyException { return new DeleteStatement( BsonReaderTool.getDocument(uncastedStatement, QUERY_FIELD), BsonReaderTool.getBooleanOrNumeric(uncastedStatement, LIMIT_FIELD, false) ); } private BsonDocument marshall() { return new BsonDocumentBuilder() .append(QUERY_FIELD, query) .append(LIMIT_FIELD, justOne) .build(); } } }