El driver mongocxx se suministra con una nueva librería, bsoncxx. Este artículo abordará algunos de los diferentes tipos en esta librería y cómo y cuándo usar cada uno. Para más información y código de ejemplo, consulte nuestro ejemplo.
desarrolladores de documentos
La librería bsoncxx ofrece cuatro interfaces para construir BSON: funciones puntuales, un desarrollador básico, un desarrollador de listas y un desarrollador basado en flujo.
bsoncxx::builder::basic::document bsoncxx::builder::stream::documento
Todos los métodos diversos para crear documentos y arreglos BSON son equivalentes. Todas las interfaces proporcionarán los mismos resultados, la elección de cuál utilizar es completamente estética.
Constructor de listas
La forma más simple de crear un documento o un arreglo BSON es usar el generador de listas tipo JSON:
// { "hello": "world" } bsoncxx::builder::list list_builder = {"hello", "world"}; bsoncxx::document::view document = list_builder.view().get_document();
Se muestran usos más avanzados del generador de listas en este ejemplo.
Funciones constructoras "únicas"
El creador de "casos únicos" crea documentos y arreglos en una sola llamada. Estos se pueden utilizar cuando no es necesario emplear lógica adicional (como condicionales o bucles) para crear el objeto:
using bsoncxx::builder::basic::kvp; // { "hello": "world" } bsoncxx::document::value document = bsoncxx::builder::basic::make_document(kvp("hello", "world"));
Constructor básico
using bsoncxx::builder::basic::kvp; // { "hello" : "world" } bsoncxx::builder::basic::document basic_builder{}; basic_builder.append(kvp("hello", "world")); bsoncxx::document::value document = basic_builder.extract();
Se muestran usos más avanzados del constructor básico en este ejemplo.
// { "hello" : "world" } using bsoncxx::builder::stream; bsoncxx::document::value document = stream::document{} << "hello" << "world" << stream::finalize;
Otros usos avanzados del generador de streams se muestran en este ejemplo.
Nota
Para añadir correctamente cada nuevo valor, un generador de flujo debe rastrear el estado del documento actual, incluyendo el nivel de anidación y el tipo del valor más reciente agregado al generador. El generador de flujo inicial no debe reutilizarse después de que este estado cambie, lo que significa que los valores intermedios deben almacenarse en nuevas variables si se está creando un documento con el generador de flujo en varias instrucciones. Dado que hacerlo adecuadamente es difícil y los mensajes de error del compilador pueden resultar confusos, se desaconseja usar el generador de flujo. En su lugar, recomendamos utilizar el generador básico o las funciones generadoras puntuales.
Construcción de arreglos en bucles
A veces es necesario construir un arreglo usando un bucle. Con el constructor básico, se puede construir un arreglo de nivel superior simplemente llamando a append dentro de un ciclo:
// [ 1, 2, 3 ] const auto elements = {1, 2, 3}; auto array_builder = bsoncxx::builder::basic::array{}; for (const auto& element : elements) { array_builder.append(element); }
Para compilar un subarreglo en un bucle, pasa una Lambda a append (o como segundo argumento de kvp si el subarreglo está contenido en un documento en lugar de un arreglo):
// { "foo" : [ 1, 2, 3 ] } using bsoncxx::builder::basic::kvp; using bsoncxx::builder::basic::sub_array; const auto elements = {1, 2, 3}; auto doc = bsoncxx::builder::basic::document{}; doc.append(kvp("foo", [&elements](sub_array child) { for (const auto& element : elements) { child.append(element); } }));
Al construir un arreglo con el generador de flujo, es importante tener en cuenta que el tipo de retorno al usar el operador << en un generador de flujo no es uniforme. Para construir un arreglo correctamente en un bucle, los valores intermedios devueltos por el generador de flujo deben almacenarse en variables cuando el tipo cambie. Un intento de construir un arreglo a partir de un generador de flujo usando un bucle podría verse de la siguiente manera:
// { "subdocs" : [ { "key" : 1 }, { "key" : 2 }, { "key" : 3 } ], "another_key" : 42 } using namespace bsoncxx; builder::stream::document builder{}; auto in_array = builder << "subdocs" << builder::stream::open_array; for (auto&& e : {1, 2, 3}) { in_array = in_array << builder::stream::open_document << "key" << e << builder::stream::close_document; } auto after_array = in_array << builder::stream::close_array; after_array << "another_key" << 42; document::value doc = after_array << builder::stream::finalize; std::cout << to_json(doc) << std::endl;
Nota
Se debe capturar el resultado de cualquier operación de stream, por lo que si se quiere dividir la única instrucción dentro del bucle for anterior en varias instrucciones, se debe capturar cada resultado intermedio. Además, la última instrucción dentro del cuerpo del bucle debe asignar su resultado de nuevo al objeto in_array, para que el bucle se reinicie en un estado coherente:
for (auto && e : {1, 2, 3}) { auto open_state = in_array << builder::stream::open_document; auto temp_state = open_state << "key" << e; in_array = temp_state << builder::stream::close_document; }
Tener documentos BSON
Este tipo representa un documento BSON real, uno que posee su propio buffer de datos. Estos documentos pueden construirse utilizando un generador llamando a extract():
bsoncxx::document::value basic_doc{basic_builder.extract()}; bsoncxx::document::value stream_doc{stream_builder.extract()};
Después de llamar a extract(), el generador se encuentra en un estado de "movido desde" y no debe ser utilizado.
Es posible crear un bsoncxx::document::value en una sola línea utilizando la interfaz del generador de streams y el token finalize. finalize devuelve un document::value de un generador de streams temporales:
// { "finalize" : "is nifty" } bsoncxx::document::value one_line = bsoncxx::builder::stream::document{} << "finalize" << "is nifty" << bsoncxx::builder::stream::finalize;
Documentos BSON sin propiedad (vistas)
Este tipo es una vista en un bsoncxx::document::value propietario.
bsoncxx::document::view document_view{document_value.view()};
Un document::value también se convierte implícitamente en un document::view:
bsoncxx::document::view document_view{document_value};
En el código crítico para el rendimiento, es preferible pasar vistas en lugar de valores porque podemos evitar copias excesivas. Además, al pasar una vista de un documento nos permite utilizar el documento varias veces:
// { "copies" : { "$gt" : 100 } } auto query_value = document{} << "copies" << open_document << "$gt" << 100 << close_document << finalize; // Run the same query across different collections auto collection1 = db["science_fiction"]; auto cursor1 = collection1.find(query_value.view()); auto collection2 = db["cookbooks"]; auto cursor2 = collection2.find(query_value.view());
Documentos BSON con tenencia opcional (view_or_value)
Muchos métodos del controlador toman un parámetro document::view_or_value, por ejemplo, run_command (ejecutar_comando):
bsoncxx::document::value run_command(bsoncxx::document::view_or_value command);
Dichos métodos pueden aceptar un document::view o un document::value. Si se pasa un document::value , debe pasarse por referencia de valor r, por lo que se transfiere la propiedad del documento al método.
document::value ping = document{} << "ping" << 1 << finalize; // You can pass a document::view into run_command() db.run_command(ping.view()); // Or you can move in a document::value db.run_command(std::move(ping));
No deberías necesitar crear view_or_value tipos directamente para usar el controlador. Se ofrecen como un método de conveniencia para permitir que los métodos del controlador tomen documentos de una manera de propiedad o no de propiedad. El tipo view_or_value también ayuda a mitigar algunos de los problemas de ciclo de vida discutidos en la siguiente sección.
Duración del documento BSON
Es imprescindible que los tipos document::value sobrevivan a cualquier tipo document::view que los emplee. Si el valor subyacente se limpia, la vista se quedará con un puntero colgante. Considera un método que devuelve una vista de un documento recién creado:
bsoncxx::document::view make_a_dangling_view() { bsoncxx::builder::basic::document builder{}; builder.append(kvp("hello", "world")); // This creates a document::value on the stack that will disappear when we return. bsoncxx::document::value stack_value{builder.extract()}; // We're returning a view of the local value return stack_value.view(); // Bad!! }
Este método retorna una vista colgante que no debe usarse:
// This view contains a dangling pointer bsoncxx::document::view dangling_view = make_a_dangling_view(); // Warning!!
Intentar crear una vista a partir de un constructor también creará un objeto de vista peligroso, porque el valor temporal devuelto por extract() no se captura:
bsoncxx::builder::stream::document temp_builder{}; temp_builder << "oh" << "no"; bsoncxx::document::view dangling_view = temp_builder.extract().view(); // Bad!!
Impresión de documentos BSON
La librería bsoncxx viene con un método de conveniencia para convertir documentos BSON a cadenas para su fácil inspección:
bsoncxx::document::value = document{} << "I am" << "a BSON document" << finalize; std::cout << bsoncxx::to_json(doc.view()) << std::endl;
Existe un método análogo, from_json(), para compilar documento::values a partir de cadenas JSON existentes.
Obtención de campos de documentos BSON
El operador [ ] accede a un documento BSON para recuperar valores:
// doc_view = { "store" : "Key Foods", "fruits" : [ "apple", "banana" ] } auto store = doc_view["store"]; auto first_fruit = doc_view["fruits"][0];
Esto devuelve un bsoncxx::document::element, que contiene el valor deseado real:
document::element store_ele{doc_view["store"]}; if (store_ele) { // this block will only execute if "store" was found in the document std::cout << "Examining inventory at " << to_json(store_ele.get_value()) << std::endl; }
Esta funcionalidad se muestra con mayor detalle en este ejemplo y este ejemplo.
BSON Types
La especificación BSON proporciona una lista de tipos compatibles. Estos se representan en C++ usando los b_xxx contenedor de tipo.
Algunos BSON types no necesariamente tienen una representación nativa para encapsular y se implementan mediante clases especiales.
Decimal128
La clase bsoncxx::decimal128 representa un valor de coma flotante decimal IEEE 754-2008 de 128bits. Esperamos que los usuarios conviertan estos valores hacia y desde cadenas, pero proporcionamos acceso a los valores de 64bits bajos y altos si los usuarios necesitan convertir a un tipo decimal128 nativo.
Puedes ver cómo trabajar con bsoncxx::decimal128 en este ejemplo.