/** * Copyright (C) 2017 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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 . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #include "mongo/platform/basic.h" #include "mongo/db/repl/idempotency_document_structure.h" #include "mongo/db/jsobj.h" #include "mongo/db/pipeline/value.h" namespace mongo { DocumentStructureEnumeratorConfig::DocumentStructureEnumeratorConfig(std::set fields_, std::size_t depth_, std::size_t length_, bool skipSubDocs_, bool skipSubArrs_) : fields(std::move(fields_)), depth(depth_), length(length_), skipSubDocs(skipSubDocs_), skipSubArrs(skipSubArrs_) {} DocumentStructureEnumerator::iterator DocumentStructureEnumerator::begin() const { return this->_docs.cbegin(); } DocumentStructureEnumerator::iterator DocumentStructureEnumerator::end() const { return this->_docs.cend(); } std::vector DocumentStructureEnumerator::getDocs() const { return this->_docs; } BSONArrayBuilder DocumentStructureEnumerator::_getArrayBuilderFromArr(BSONArray arr) { BSONArrayBuilder arrBuilder; for (auto elem : arr) { arrBuilder.append(elem); } return arrBuilder; } void DocumentStructureEnumerator::_enumerateFixedLenArrs( const DocumentStructureEnumeratorConfig& config, const ScalarGenerator* scalarGenerator, BSONArray arr, std::vector* arrs) { if (!config.length) { // Base case: no more room for any other elements. arrs->push_back(arr); return; } // Otherwise, go through our choices, similar to the approach to documents. // DocumentStructureEnumeratorConfig nextElementConfig(config); nextElementConfig.length--; // Scalar. BSONArrayBuilder scalarArr = _getArrayBuilderFromArr(arr); scalarGenerator->generateScalar().addToBsonArray(&scalarArr); _enumerateFixedLenArrs(nextElementConfig, scalarGenerator, scalarArr.arr(), arrs); if (config.depth <= 0) { return; } DocumentStructureEnumeratorConfig nextLayerConfig(config); nextLayerConfig.depth--; nextLayerConfig.length += arr.nFields(); // Subarray. std::vector subArrs = _enumerateArrs(nextLayerConfig, scalarGenerator); for (auto subArr : subArrs) { BSONArrayBuilder arrayArr = _getArrayBuilderFromArr(arr); arrayArr.append(subArr); _enumerateFixedLenArrs(nextElementConfig, scalarGenerator, arrayArr.arr(), arrs); } // Document. if (!config.skipSubDocs) { BSONObj blankDoc; std::vector subDocs; _enumerateDocs(nextLayerConfig, scalarGenerator, blankDoc, &subDocs); for (auto subDoc : subDocs) { BSONArrayBuilder docArr = _getArrayBuilderFromArr(arr); docArr.append(subDoc); _enumerateFixedLenArrs(nextElementConfig, scalarGenerator, docArr.arr(), arrs); } } return; } void DocumentStructureEnumerator::_enumerateDocs(const DocumentStructureEnumeratorConfig& config, const ScalarGenerator* scalarGenerator, BSONObj doc, std::vector* docs) { if (config.fields.empty()) { // Base case: when we have run out of fields to use. docs->push_back(doc); return; } // Create a copy of the fields we have. std::set remainingFields(config.fields); // Pop the first field arbitrarily. StringData field = *remainingFields.begin(); remainingFields.erase(remainingFields.begin()); DocumentStructureEnumeratorConfig nextFieldConfig(config); nextFieldConfig.fields = remainingFields; // Branch off depending on the choice. // // Scalar. BSONObjBuilder scalarDoc(doc); scalarGenerator->generateScalar().addToBsonObj(&scalarDoc, field); _enumerateDocs(nextFieldConfig, scalarGenerator, scalarDoc.obj(), docs); // Omit the field. BSONObjBuilder vanishDoc(doc); _enumerateDocs(nextFieldConfig, scalarGenerator, vanishDoc.obj(), docs); if (config.depth <= 0) { // If we are ever at the deepest level possible, we have no more choices after this. return; } DocumentStructureEnumeratorConfig nextLayerConfig(nextFieldConfig); nextLayerConfig.depth--; if (!config.skipSubArrs) { // Array. for (auto subArr : _enumerateArrs(nextLayerConfig, scalarGenerator)) { BSONObjBuilder arrayDoc(doc); arrayDoc.append(field, subArr); _enumerateDocs(nextFieldConfig, scalarGenerator, arrayDoc.obj(), docs); } } // Subdocument. if (!config.skipSubDocs) { BSONObj blankDoc; std::vector subDocs; _enumerateDocs(nextLayerConfig, scalarGenerator, blankDoc, &subDocs); for (auto subDoc : subDocs) { BSONObjBuilder docDoc(doc); docDoc.append(field, subDoc); _enumerateDocs(nextFieldConfig, scalarGenerator, docDoc.obj(), docs); } } } std::vector DocumentStructureEnumerator::_enumerateArrs( const DocumentStructureEnumeratorConfig& config, const ScalarGenerator* scalarGenerator) { std::vector arrs; // We enumerate arrays of each possible length independently of each other to avoid having to // account for how different omissions of elements in an array are equivalent to each other. // For example, we'd otherwise need to treat omitting the first element and adding x as distinct // from adding x and omitting the second element since both yield an array containing only the // element x. Without this, we will enumerate duplicate arrays. for (std::size_t i = 0; i <= config.length; i++) { BSONArray emptyArr; DocumentStructureEnumeratorConfig nextConfig(config); nextConfig.length = i; _enumerateFixedLenArrs(nextConfig, scalarGenerator, emptyArr, &arrs); } return arrs; } std::vector DocumentStructureEnumerator::enumerateDocs() const { BSONObj startDoc; std::vector docs; _enumerateDocs(this->_config, this->_scalarGenerator, startDoc, &docs); return docs; } std::vector DocumentStructureEnumerator::enumerateArrs() const { return _enumerateArrs(this->_config, this->_scalarGenerator); } DocumentStructureEnumerator::DocumentStructureEnumerator(DocumentStructureEnumeratorConfig config, const ScalarGenerator* scalarGenerator) : _config(std::move(config)), _scalarGenerator(scalarGenerator) { this->_docs = enumerateDocs(); } } // namespace mongo