/* * 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.aggregation; import com.eightkdata.mongowp.bson.BsonDocument; import com.eightkdata.mongowp.bson.BsonType; import com.eightkdata.mongowp.bson.BsonValue; import com.eightkdata.mongowp.bson.utils.DefaultBsonValues; import com.eightkdata.mongowp.exceptions.BadValueException; import com.eightkdata.mongowp.exceptions.NoSuchKeyException; import com.eightkdata.mongowp.exceptions.TypesMismatchException; import com.eightkdata.mongowp.fields.DocField; import com.eightkdata.mongowp.fields.LongField; import com.eightkdata.mongowp.fields.NumberField; import com.eightkdata.mongowp.fields.StringField; import com.eightkdata.mongowp.server.api.impl.AbstractNotAliasableCommand; import com.eightkdata.mongowp.utils.BsonDocumentBuilder; import com.eightkdata.mongowp.utils.BsonReaderTool; import com.torodb.mongodb.commands.signatures.aggregation.CountCommand.CountArgument; import javax.annotation.Nonnegative; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * */ public class CountCommand extends AbstractNotAliasableCommand<CountArgument, Long> { private static final NumberField N_FIELD = new NumberField("n"); public static final CountCommand INSTANCE = new CountCommand(); private CountCommand() { super("count"); } @Override public Class<? extends CountArgument> getArgClass() { return CountArgument.class; } @Override public CountArgument unmarshallArg(BsonDocument requestDoc) throws TypesMismatchException, NoSuchKeyException, BadValueException { return CountArgument.unmarshall(requestDoc); } @Override public BsonDocument marshallArg(CountArgument request) { throw new UnsupportedOperationException("Not supported yet."); //TODO } @Override public Class<? extends Long> getResultClass() { return Long.class; } @Override public BsonDocument marshallResult(Long reply) { return new BsonDocumentBuilder() .appendNumber(N_FIELD, reply) .build(); } @Override public Long unmarshallResult(BsonDocument resultDoc) throws TypesMismatchException, NoSuchKeyException { return BsonReaderTool.getNumeric(resultDoc, N_FIELD).longValue(); } public static class CountArgument { private final BsonDocument query; //TODO(gortiz) parse query private final String collection; @Nullable private final String hint; private final long limit; private final long skip; public CountArgument( @Nonnull String collection, @Nonnull BsonDocument query, @Nullable String hint, @Nonnegative long limit, @Nonnegative long skip) { this.collection = collection; this.query = query; this.hint = hint; this.limit = limit; this.skip = skip; } /** * A query that selects which documents to count in a collection. * * @return the filter or null if all documents shall be count */ @Nullable public BsonDocument getQuery() { return query; } @Nonnegative public long getLimit() { return limit; } @Nonnegative public long getSkip() { return skip; } /** * The index to use. Specify either the index name as a string. * * @return */ @Nullable public String getHint() { return hint; } public String getCollection() { return collection; } private static final StringField COUNT_FIELD = new StringField("count"); private static final LongField SKIP_FIELD = new LongField("skip"); private static final LongField LIMIT_FIELD = new LongField("limit"); private static final DocField QUERY_FIELD = new DocField("query"); private static final String HINT_FIELD_NAME = "hint"; public static CountArgument unmarshall(BsonDocument doc) throws TypesMismatchException, BadValueException, NoSuchKeyException { long skip = BsonReaderTool.getLong(doc, SKIP_FIELD, 0); if (skip < 0) { throw new BadValueException("Skip value is negative in the count query"); } long limit = BsonReaderTool.getLong(doc, LIMIT_FIELD, 0); if (limit < 0) { // For counts, limit and -limit mean the same thing. limit = -limit; } BsonDocument query; try { query = BsonReaderTool.getDocument(doc, QUERY_FIELD); } catch (NoSuchKeyException ex) { query = DefaultBsonValues.EMPTY_DOC; } catch (TypesMismatchException ex) { //Some drivers send non object values on query field when no query is specified //see mongo SERVER-15456 query = DefaultBsonValues.EMPTY_DOC; } String hint = null; if (doc.containsKey(HINT_FIELD_NAME)) { BsonValue uncastedHint = doc.get(HINT_FIELD_NAME); if (uncastedHint.getType().equals(BsonType.STRING)) { hint = uncastedHint.asString().getValue(); } else if (uncastedHint.getType().equals(BsonType.DOCUMENT)) { BsonDocument docHint = uncastedHint.asDocument(); if (!docHint.isEmpty()) { hint = docHint.getFirstEntry().getKey(); } } } String collection = BsonReaderTool.getString(doc, COUNT_FIELD); return new CountArgument(collection, query, hint, limit, skip); } } }