Piac test code coverage report
Current view: top level - src - db.cpp (source / functions) Hit Total Coverage
Commit: Piac-DEBUG Lines: 206 240 85.8 %
Date: 2022-12-16 13:46:15 Functions: 13 13 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 337 842 40.0 %

           Branch data     Line data    Source code
       1                 :            : // *****************************************************************************
       2                 :            : /*!
       3                 :            :   \file      src/db.cpp
       4                 :            :   \copyright 2022-2025 J. Bakosi,
       5                 :            :              All rights reserved. See the LICENSE file for details.
       6                 :            :   \brief     Piac database functionality
       7                 :            : */
       8                 :            : // *****************************************************************************
       9                 :            : 
      10                 :            : #include <string>
      11                 :            : 
      12                 :            : #include "string_util.hpp"
      13                 :            : #include "logging_util.hpp"
      14                 :            : #include "crypto_util.hpp"
      15                 :            : #include "db.hpp"
      16                 :            : #include "document.hpp"
      17                 :            : 
      18                 :            : Xapian::doccount
      19                 :         23 : piac::get_doccount( const std::string db_name )
      20                 :            : // *****************************************************************************
      21                 :            : //  Get number of documents in Xapian database
      22                 :            : //! \param[in] db_name Name of Xapian db to operate on
      23                 :            : //! \return Number of documents in database
      24                 :            : // *****************************************************************************
      25                 :            : {
      26                 :            :   try {
      27         [ +  + ]:         39 :     Xapian::Database db( db_name );
      28         [ +  - ]:         16 :     return db.get_doccount();
      29         [ -  + ]:         14 :   } catch ( const Xapian::Error &e ) {
      30 [ +  - ][ +  - ]:          7 :     MWARNING( e.get_description() );
         [ +  - ][ +  - ]
                 [ +  - ]
      31                 :            :   }
      32                 :          7 :   return {};
      33                 :            : }
      34                 :            : 
      35                 :            : std::string
      36                 :          7 : piac::add_document( const std::string& author,
      37                 :            :                     Xapian::TermGenerator& indexer,
      38                 :            :                     Xapian::WritableDatabase& db,
      39                 :            :                     Document& ndoc )
      40                 :            : // ****************************************************************************
      41                 :            : //  Add document to Xapian database
      42                 :            : //! \param[in] author Author of the database document
      43                 :            : //! \param[in,out] indexer Xapian indexer to use for database indexing
      44                 :            : //! \param[in,out] db Xapian database object to add document to
      45                 :            : //! \param[in,out] ndoc Json document to add
      46                 :            : //! \return Hash of the document added
      47                 :            : // ****************************************************************************
      48                 :            : {
      49         [ -  + ]:          7 :   assert( not author.empty() );
      50         [ +  - ]:         14 :   Xapian::Document doc;
      51         [ +  - ]:          7 :   indexer.set_document( doc );
      52                 :            :   // Index each field with a suitable prefix
      53 [ +  - ][ +  - ]:          7 :   indexer.index_text( ndoc.title(), 1, "S" );
      54 [ +  - ][ +  - ]:          7 :   indexer.index_text( ndoc.description(), 1, "XD" );
      55 [ +  - ][ +  - ]:          7 :   indexer.index_text( ndoc.category(), 1, "XC" );
      56 [ +  - ][ +  - ]:          7 :   indexer.index_text( ndoc.condition(), 1, "XO" );
      57 [ +  - ][ +  - ]:          7 :   indexer.index_text( ndoc.shipping(), 1, "XS" );
      58 [ +  - ][ +  - ]:          7 :   indexer.index_text( ndoc.format(), 1, "XF" );
      59 [ +  - ][ +  - ]:          7 :   indexer.index_text( ndoc.location(), 1, "XL" );
      60 [ +  - ][ +  - ]:          7 :   indexer.index_text( ndoc.keywords(), 1, "K" );
      61                 :            :   // Index fields without prefixes for general search
      62         [ +  - ]:          7 :   indexer.index_text( ndoc.title() );
      63         [ +  - ]:          7 :   indexer.increase_termpos();
      64         [ +  - ]:          7 :   indexer.index_text( ndoc.description() );
      65         [ +  - ]:          7 :   indexer.increase_termpos();
      66         [ +  - ]:          7 :   indexer.index_text( ndoc.category() );
      67         [ +  - ]:          7 :   indexer.increase_termpos();
      68         [ +  - ]:          7 :   indexer.index_text( ndoc.condition() );
      69         [ +  - ]:          7 :   indexer.increase_termpos();
      70         [ +  - ]:          7 :   indexer.index_text( ndoc.shipping() );
      71         [ +  - ]:          7 :   indexer.increase_termpos();
      72         [ +  - ]:          7 :   indexer.index_text( ndoc.format() );
      73         [ +  - ]:          7 :   indexer.increase_termpos();
      74         [ +  - ]:          7 :   indexer.index_text( ndoc.location() );
      75         [ +  - ]:          7 :   indexer.increase_termpos();
      76         [ +  - ]:          7 :   indexer.index_text( ndoc.keywords() );
      77                 :            :   // Add value fields
      78 [ +  - ][ +  - ]:          7 :   doc.add_value( 1, std::to_string( ndoc.price() ) );
      79                 :            :   // Generate a hash of the doc fields and store it in the document
      80         [ +  - ]:          7 :   ndoc.author( author );
      81         [ +  - ]:         14 :   auto entry = ndoc.serialize();
      82 [ +  - ][ +  - ]:          7 :   ndoc.sha( sha256( entry ) );
      83         [ +  - ]:          7 :   auto sha = ndoc.sha();
      84                 :            :   // Ensure each object ends up in the database only once no matter how
      85                 :            :   // many times we run the indexer
      86 [ +  - ][ +  - ]:          7 :   doc.add_boolean_term( std::to_string( ndoc.id() ) );
      87         [ +  - ]:          7 :   doc.set_data( entry );
      88 [ +  - ][ +  - ]:          7 :   doc.add_term( 'Q' + sha );
      89                 :            :   // Add Xapian doc to db
      90 [ +  - ][ +  - ]:          7 :   db.replace_document( 'Q' + sha, doc );
      91                 :         14 :   return sha;
      92                 :            : }
      93                 :            : 
      94                 :            : std::string
      95                 :          6 : piac::index_db( const std::string& author,
      96                 :            :                 const std::string& db_name,
      97                 :            :                 const std::string& input_filename,
      98                 :            :                 const std::unordered_set< std::string >& my_hashes )
      99                 :            : // ****************************************************************************
     100                 :            : //  Index Xapian database
     101                 :            : //! \param[in] author Author of the database document
     102                 :            : //! \param[in] db_name Name of the Xapian database object
     103                 :            : //! \param[in] input_filename File to read JSON data from
     104                 :            : //! \param[in] my_hashes Hashes to check for duplicates when adding documents
     105                 :            : //! \return Info string showing how many documents have been added
     106                 :            : // ****************************************************************************
     107                 :            : {
     108         [ -  + ]:          6 :   assert( not author.empty() );
     109                 :            : 
     110         [ +  - ]:         12 :   std::ifstream f( input_filename );
     111 [ +  - ][ -  + ]:          6 :   if (not f.good()) {
     112         [ -  - ]:          0 :     return "Cannot open database input file: " + input_filename;
     113                 :            :   }
     114                 :            : 
     115 [ +  - ][ +  - ]:          6 :   MDEBUG( "Indexing " << input_filename );
         [ +  - ][ +  - ]
                 [ +  - ]
     116         [ +  - ]:         12 :   Xapian::WritableDatabase db( db_name, Xapian::DB_CREATE_OR_OPEN );
     117         [ +  - ]:         12 :   Xapian::TermGenerator indexer;
     118 [ +  - ][ +  - ]:         12 :   Xapian::Stem stemmer( "english" );
     119         [ +  - ]:          6 :   indexer.set_stemmer( stemmer );
     120         [ +  - ]:          6 :   indexer.set_stemming_strategy( indexer.STEM_SOME_FULL_POS );
     121                 :          6 :   std::size_t numins = 0;
     122                 :            :   try {
     123                 :            :     // Read json db from file
     124                 :         12 :     Documents ndoc;
     125         [ +  - ]:          6 :     ndoc.deserializeFromFile( input_filename );
     126                 :            :     // Insert documents we do not yet have from json into xapian db
     127         [ +  + ]:         16 :     for (auto& d : ndoc.documents()) {
     128         [ +  - ]:         10 :       d.author( author );
     129         [ +  - ]:         20 :       auto entry = d.serialize();
     130 [ +  - ][ +  - ]:         10 :       if (my_hashes.find( sha256( entry ) ) == end(my_hashes)) {
                 [ +  + ]
     131         [ +  - ]:          5 :         add_document( author, indexer, db, d );
     132                 :          5 :         ++numins;
     133                 :            :       }
     134                 :            :     }
     135 [ +  - ][ +  - ]:          6 :     MDEBUG( "Indexed " << numins << " entries" );
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
     136                 :            :     // Explicitly commit so that we get to see any errors. WritableDatabase's
     137                 :            :     // destructor will commit implicitly (unless we're in a transaction) but
     138                 :            :     // will swallow any exceptions produced.
     139         [ +  - ]:          6 :     db.commit();
     140                 :            : 
     141         [ -  - ]:          0 :   } catch ( const Xapian::Error &e ) {
     142 [ -  - ][ -  - ]:          0 :     MERROR( e.get_description() );
         [ -  - ][ -  - ]
                 [ -  - ]
     143                 :            :   }
     144 [ +  - ][ +  - ]:         12 :   return "Added " + std::to_string( numins ) + " entries";
                 [ +  - ]
     145                 :            : }
     146                 :            : 
     147                 :            : [[nodiscard]] std::string
     148                 :          1 : piac::db_query( const std::string& db_name, std::string&& cmd )
     149                 :            : // *****************************************************************************
     150                 :            : //  Query Xapian database
     151                 :            : //! \param[in] db_name Name of the Xapian database object
     152                 :            : //! \param[in,out] cmd Query command
     153                 :            : //! \return Result of the database query
     154                 :            : // *****************************************************************************
     155                 :            : {
     156                 :            :   try {
     157                 :            : 
     158 [ +  - ][ +  - ]:          1 :     MDEBUG( "db query: '" << cmd << "'" );
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
     159                 :            :     // Open the database for searching
     160         [ +  - ]:          2 :     Xapian::Database db( db_name );
     161                 :            :     // Start an enquire session
     162         [ +  - ]:          2 :     Xapian::Enquire enquire( db );
     163                 :            :     // Parse the query string to produce a Xapian::Query object
     164         [ +  - ]:          2 :     Xapian::QueryParser qp;
     165 [ +  - ][ +  - ]:          3 :     Xapian::Stem stemmer("english");
     166         [ +  - ]:          1 :     qp.set_stemmer( stemmer );
     167         [ +  - ]:          1 :     qp.set_database( db );
     168         [ +  - ]:          1 :     qp.set_stemming_strategy( Xapian::QueryParser::STEM_SOME );
     169         [ +  - ]:          2 :     Xapian::Query query = qp.parse_query( cmd );
     170 [ +  - ][ +  - ]:          1 :     MDEBUG( "parsed query: '" << query.get_description() << "'" );
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
                 [ +  - ]
     171                 :            :     // Find the top 10 results for the query
     172         [ +  - ]:          1 :     enquire.set_query( query );
     173 [ +  - ][ +  - ]:          1 :     MDEBUG( "set query: '" << query.get_description() << "'" );
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
                 [ +  - ]
     174         [ +  - ]:          2 :     Xapian::MSet matches = enquire.get_mset( 0, 10 );
     175 [ +  - ][ +  - ]:          1 :     MDEBUG( "got matches" );
         [ +  - ][ +  - ]
     176                 :            :     // Construct the results
     177         [ +  - ]:          1 :     auto nr = matches.get_matches_estimated();
     178 [ +  - ][ +  - ]:          1 :     MDEBUG( "got estimated matches: " << nr );
         [ +  - ][ +  - ]
                 [ +  - ]
     179         [ +  - ]:          2 :     std::stringstream result;
     180 [ +  - ][ +  - ]:          1 :     result << nr << " results found.";
     181         [ -  + ]:          1 :     if (nr) {
     182 [ -  - ][ -  - ]:          0 :       result << "\nmatches 1-" << matches.size() << ":\n\n";
         [ -  - ][ -  - ]
     183 [ -  - ][ -  - ]:          0 :       for (Xapian::MSetIterator i = matches.begin(); i != matches.end(); ++i) {
                 [ -  - ]
     184 [ -  - ][ -  - ]:          0 :         MDEBUG( "getting match: " << i.get_rank() );
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
     185 [ -  - ][ -  - ]:          0 :         result << i.get_rank() + 1 << ": " << i.get_weight() << " docid=" << *i
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
     186 [ -  - ][ -  - ]:          0 :                  << " [" << i.get_document().get_data() << "]\n";
         [ -  - ][ -  - ]
                 [ -  - ]
     187                 :            :       }
     188                 :            :     }
     189                 :            :     //MDEBUG( "results: " + result.str() );
     190         [ +  - ]:          1 :     return result.str();
     191                 :            : 
     192         [ -  - ]:          0 :   } catch ( const Xapian::Error &e ) {
     193 [ -  - ][ -  - ]:          0 :     MERROR( e.get_description() );
         [ -  - ][ -  - ]
                 [ -  - ]
     194                 :            :   }
     195                 :          0 :   return {};
     196                 :            : }
     197                 :            : 
     198                 :            : [[nodiscard]] std::vector< std::string >
     199                 :          3 : piac::db_get_docs( const std::string& db_name,
     200                 :            :                    const std::vector< std::string >& hashes )
     201                 :            : // *****************************************************************************
     202                 :            : //  Get documents from Xapian database
     203                 :            : //! \param[in] db_name Name of the Xapian database object
     204                 :            : //! \param[in] hashes Hashes of database documents to get retrieve
     205                 :            : //! \return Result of the database query
     206                 :            : // *****************************************************************************
     207                 :            : {
     208                 :          6 :   std::vector< std::string > docs;
     209                 :            :   try {
     210                 :            : 
     211         [ +  - ]:          3 :     Xapian::Database db( db_name );
     212         [ +  - ]:          3 :     Xapian::doccount dbsize = db.get_doccount();
     213         [ -  + ]:          3 :     if (dbsize == 0) return {};
     214                 :            : 
     215         [ +  + ]:          6 :     for (const auto& h : hashes) {
     216         [ -  + ]:          3 :       assert( h.size() == 32 );
     217 [ +  - ][ +  - ]:          6 :       auto p = db.postlist_begin( 'Q' + h );
     218 [ +  - ][ +  - ]:          3 :       if (p != db.postlist_end( 'Q' + h ))
     219 [ +  - ][ +  - ]:          3 :         docs.push_back( db.get_document( *p ).get_data() );
         [ +  - ][ +  - ]
     220                 :            :       else
     221 [ -  - ][ -  - ]:          0 :         MWARNING( "Document not found: " << hex(h) );
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
     222                 :            :     }
     223                 :            : 
     224         [ -  - ]:          0 :   } catch ( const Xapian::Error &e ) {
     225 [ -  - ][ -  - ]:          0 :     if (e.get_description().find("No such file") == std::string::npos)
     226 [ -  - ][ -  - ]:          0 :       MERROR( e.get_description() );
         [ -  - ][ -  - ]
                 [ -  - ]
     227                 :            :   }
     228                 :            : 
     229                 :          3 :   return docs;
     230                 :            : }
     231                 :            : 
     232                 :            : std::size_t
     233                 :          2 : piac::db_put_docs( const std::string& db_name,
     234                 :            :                    const std::vector< std::string >& docs )
     235                 :            : // *****************************************************************************
     236                 :            : //  Put documents to Xapian database
     237                 :            : //! \param[in] db_name Name of the Xapian database object
     238                 :            : //! \param[in] docs Documents to insert to Xapian database
     239                 :            : //! \return Number of documents inserted
     240                 :            : // *****************************************************************************
     241                 :            : {
     242                 :            :   try {
     243 [ +  - ][ +  - ]:          2 :     MDEBUG( "Inserting & indexing " << docs.size() << " new entries" );
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
     244         [ +  - ]:          4 :     Xapian::WritableDatabase db( db_name, Xapian::DB_CREATE_OR_OPEN );
     245         [ +  - ]:          4 :     Xapian::TermGenerator indexer;
     246 [ +  - ][ +  - ]:          6 :     Xapian::Stem stemmer( "english" );
     247         [ +  - ]:          2 :     indexer.set_stemmer( stemmer );
     248         [ +  - ]:          2 :     indexer.set_stemming_strategy( indexer.STEM_SOME_FULL_POS );
     249                 :            : 
     250                 :            :     // Insert all documents into xapian db
     251         [ +  + ]:          4 :     for (const auto& d : docs) {
     252                 :          4 :       Document ndoc;
     253         [ +  - ]:          2 :       ndoc.deserialize( d );
     254                 :            :       // refuse doc without author
     255         [ +  - ]:          4 :       auto author = ndoc.author();
     256 [ +  - ][ +  - ]:          2 :       if (not author.empty()) add_document( author, indexer, db, ndoc );
     257                 :            :     }
     258                 :            : 
     259 [ +  - ][ +  - ]:          2 :     MDEBUG( "Finished indexing " << docs.size() <<
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
     260                 :            :             " new entries, commit to db" );
     261                 :            :     // Explicitly commit so that we get to see any errors. WritableDatabase's
     262                 :            :     // destructor will commit implicitly (unless we're in a transaction) but
     263                 :            :     // will swallow any exceptions produced.
     264         [ +  - ]:          2 :     db.commit();
     265                 :          2 :     return docs.size();
     266                 :            : 
     267         [ -  - ]:          0 :   } catch ( const Xapian::Error &e ) {
     268 [ -  - ][ -  - ]:          0 :     MERROR( e.get_description() );
         [ -  - ][ -  - ]
                 [ -  - ]
     269                 :            :   }
     270                 :            : 
     271                 :          0 :   return 0;
     272                 :            : }
     273                 :            : 
     274                 :            : std::string
     275                 :          3 : piac::db_rm_docs( const std::string& author,
     276                 :            :                   const std::string& db_name,
     277                 :            :                   const std::unordered_set< std::string >& hashes_to_delete,
     278                 :            :                   const std::unordered_set< std::string >& my_hashes )
     279                 :            : // *****************************************************************************
     280                 :            : //  Remove documents from Xapian database
     281                 :            : //! \param[in] author Author of the database document
     282                 :            : //! \param[in] db_name Name of the Xapian database object
     283                 :            : //! \param[in] hashes_to_delete Hashes of documents to delete
     284                 :            : //! \param[in] my_hashes Hashes to check for duplicates when removing documents
     285                 :            : //! \return Info on number of documents removed
     286                 :            : // *****************************************************************************
     287                 :            : {
     288                 :          3 :   std::size_t numrm = 0;
     289                 :            :   try {
     290                 :            : 
     291         [ +  - ]:          3 :     Xapian::WritableDatabase db( db_name );
     292         [ +  - ]:          3 :     Xapian::doccount dbsize = db.get_doccount();
     293 [ -  + ][ -  - ]:          3 :     if (dbsize == 0) return "no docs";
     294                 :            : 
     295         [ +  + ]:         16 :     for (const auto& h : my_hashes) {
     296         [ -  + ]:         13 :       assert( h.size() == 32 );
     297 [ +  - ][ +  - ]:         13 :       auto p = db.postlist_begin( 'Q' + h );
     298 [ +  - ][ +  - ]:         39 :       if ( p != db.postlist_end( 'Q' + h ) &&
         [ +  + ][ +  - ]
         [ +  + ][ -  - ]
                 [ -  - ]
     299 [ +  - ][ +  - ]:         26 :            hashes_to_delete.find(hex(h)) != end(hashes_to_delete) )
         [ +  - ][ +  - ]
                 [ -  - ]
     300                 :            :       {
     301 [ +  - ][ +  - ]:          3 :         auto entry = db.get_document( *p ).get_data();
                 [ +  - ]
     302                 :          3 :         Document ndoc;
     303         [ +  - ]:          3 :         ndoc.deserialize( entry );
     304         [ +  - ]:          3 :         if (author == ndoc.author()) {
     305 [ +  - ][ +  - ]:          3 :           MDEBUG( "db rm" + sha256(h) );
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
     306 [ +  - ][ +  - ]:          3 :           db.delete_document( 'Q' + h );
     307                 :          3 :           ++numrm;
     308                 :            :         } else {
     309 [ -  - ][ -  - ]:          0 :           MDEBUG( "db rm auth: " + hex(author) + " != " + hex(ndoc.author()) );
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
                 [ -  - ]
     310         [ -  - ]:          0 :           return "db rm: author != user";
     311                 :            :         }
     312                 :            :       }
     313                 :            :     }
     314                 :            : 
     315         [ -  - ]:          0 :   } catch ( const Xapian::Error &e ) {
     316 [ -  - ][ -  - ]:          0 :     if (e.get_description().find("No such file") == std::string::npos)
     317 [ -  - ][ -  - ]:          0 :       MERROR( e.get_description() );
         [ -  - ][ -  - ]
                 [ -  - ]
     318                 :            :   }
     319                 :            : 
     320 [ +  - ][ +  - ]:          6 :   return "Removed " + std::to_string( numrm ) + " entries";
     321                 :            : }
     322                 :            : 
     323                 :            : [[nodiscard]] std::vector< std::string >
     324                 :         31 : piac::db_list_hash( const std::string& db_name, bool inhex )
     325                 :            : // *****************************************************************************
     326                 :            : //  List hashes from Xapian database
     327                 :            : //! \param[in] db_name Name of the Xapian database object
     328                 :            : //! \param[in] inhex True to list hashes hex-encoded
     329                 :            : //! \return List of hashes
     330                 :            : // *****************************************************************************
     331                 :            : {
     332                 :         62 :   std::vector< std::string > hashes;
     333                 :            :   try {
     334                 :            : 
     335         [ +  + ]:         31 :     Xapian::Database db( db_name );
     336         [ +  - ]:         24 :     Xapian::doccount dbsize = db.get_doccount();
     337         [ -  + ]:         24 :     if (dbsize == 0) return {};
     338                 :            : 
     339 [ +  - ][ +  + ]:        132 :     for (auto it = db.postlist_begin({}); it != db.postlist_end({}); ++it) {
                 [ +  - ]
     340 [ +  - ][ +  - ]:        168 :       auto entry = db.get_document( *it ).get_data();
                 [ +  - ]
     341         [ +  - ]:         84 :       auto digest = sha256( entry );
     342 [ +  + ][ +  - ]:         84 :       hashes.emplace_back( inhex ? hex(digest) : digest );
         [ +  - ][ +  - ]
     343                 :            :     }
     344                 :            : 
     345         [ -  + ]:         14 :   } catch ( const Xapian::Error &e ) {
     346 [ +  - ][ -  + ]:          7 :     if (e.get_description().find("No such file") == std::string::npos)
     347 [ -  - ][ -  - ]:          0 :       MERROR( e.get_description() );
         [ -  - ][ -  - ]
                 [ -  - ]
     348                 :            :   }
     349                 :            : 
     350                 :         31 :   return hashes;
     351                 :            : }
     352                 :            : 
     353                 :            : [[nodiscard]] std::vector< std::string >
     354                 :          1 : piac::db_list_doc( const std::string& db_name )
     355                 :            : // *****************************************************************************
     356                 :            : //  List documents from Xapian database
     357                 :            : //! \param[in] db_name Name of the Xapian database object
     358                 :            : //! \return List of documents
     359                 :            : // *****************************************************************************
     360                 :            : {
     361                 :          2 :   std::vector< std::string > docs;
     362                 :            :   try {
     363                 :            : 
     364         [ +  - ]:          1 :     Xapian::Database db( db_name );
     365         [ +  - ]:          1 :     Xapian::doccount dbsize = db.get_doccount();
     366         [ -  + ]:          1 :     if (dbsize == 0) return {};
     367                 :            : 
     368 [ +  - ][ +  + ]:          7 :     for (auto it = db.postlist_begin({}); it != db.postlist_end({}); ++it) {
                 [ +  - ]
     369 [ +  - ][ +  - ]:         10 :       auto entry = db.get_document( *it ).get_data();
                 [ +  - ]
     370         [ +  - ]:         10 :       auto digest = sha256( entry );
     371                 :          5 :       Document d;
     372         [ +  - ]:          5 :       d.deserialize( entry );
     373 [ +  - ][ +  - ]:          5 :       d.author( hex( d.author() ) );
     374 [ +  - ][ +  - ]:          5 :       docs.emplace_back( hex( digest ) + ": " + d.serialize() );
         [ +  - ][ +  - ]
                 [ +  - ]
     375                 :            :     }
     376                 :            : 
     377         [ -  - ]:          0 :   } catch ( const Xapian::Error &e ) {
     378 [ -  - ][ -  - ]:          0 :     if (e.get_description().find("No such file") == std::string::npos)
     379 [ -  - ][ -  - ]:          0 :       MERROR( e.get_description() );
         [ -  - ][ -  - ]
                 [ -  - ]
     380                 :            :   }
     381                 :            : 
     382                 :          1 :   return docs;
     383                 :            : }
     384                 :            : 
     385                 :            : [[nodiscard]] std::size_t
     386                 :          1 : piac::db_list_numuser( const std::string& db_name )
     387                 :            : // *****************************************************************************
     388                 :            : //  List number of unique users in Xapian database
     389                 :            : //! \param[in] db_name Name of the Xapian database object
     390                 :            : //! \return Number of unique users created documents in database
     391                 :            : // *****************************************************************************
     392                 :            : {
     393                 :            :   try {
     394                 :            : 
     395         [ +  - ]:          2 :     Xapian::Database db( db_name );
     396         [ +  - ]:          1 :     Xapian::doccount dbsize = db.get_doccount();
     397         [ -  + ]:          1 :     if (dbsize == 0) return {};
     398                 :            : 
     399                 :          2 :     std::unordered_set< std::string > user;
     400 [ +  - ][ +  + ]:          7 :     for (auto it = db.postlist_begin({}); it != db.postlist_end({}); ++it) {
                 [ +  - ]
     401 [ +  - ][ +  - ]:         10 :       auto entry = db.get_document( *it ).get_data();
                 [ +  - ]
     402                 :         10 :       Document d;
     403         [ +  - ]:          5 :       d.deserialize( entry );
     404         [ +  - ]:          5 :       user.insert( d.author() );
     405                 :            :     }
     406                 :          1 :     return user.size();
     407                 :            : 
     408                 :            : 
     409         [ -  - ]:          0 :   } catch ( const Xapian::Error &e ) {
     410 [ -  - ][ -  - ]:          0 :     if (e.get_description().find("No such file") == std::string::npos)
     411 [ -  - ][ -  - ]:          0 :       MERROR( e.get_description() );
         [ -  - ][ -  - ]
                 [ -  - ]
     412                 :            :   }
     413                 :            : 
     414                 :          0 :   return {};
     415                 :            : }
     416                 :            : 
     417                 :            : std::string
     418                 :          6 : piac::db_add( const std::string& author,
     419                 :            :               const std::string& db_name,
     420                 :            :               std::string&& cmd,
     421                 :            :               const std::unordered_set< std::string >& my_hashes )
     422                 :            : // *****************************************************************************
     423                 :            : //  Add documents to Xapian database
     424                 :            : //! \param[in] author Author of the database document
     425                 :            : //! \param[in] db_name Name of the Xapian database object
     426                 :            : //! \param[in,out] cmd Add command
     427                 :            : //! \param[in] my_hashes Hashes to check for duplicates when adding documents
     428                 :            : //! \return Info string after add database operation
     429                 :            : // *****************************************************************************
     430                 :            : {
     431                 :          6 :   trim( cmd );
     432 [ +  - ][ +  - ]:          6 :   MDEBUG( "db add " + cmd );
         [ +  - ][ +  - ]
     433         [ -  + ]:          6 :   assert( not author.empty() );
     434 [ +  - ][ +  - ]:          6 :   if (cmd[0]=='j' && cmd[1]=='s' && cmd[2]=='o' && cmd[3]=='n') {
         [ +  - ][ +  - ]
                 [ +  - ]
     435                 :          6 :     cmd.erase( 0, 5 );
     436 [ +  - ][ +  - ]:          6 :     MDEBUG( "Add json file: '" << cmd << "' to db" );
         [ +  - ][ +  - ]
                 [ +  - ]
     437                 :          6 :     return index_db( author, db_name, cmd, my_hashes );
     438                 :            :   }
     439         [ -  - ]:          0 :   return "unknown cmd";
     440                 :            : }
     441                 :            : 
     442                 :            : std::string
     443                 :          3 : piac::db_rm( const std::string& author,
     444                 :            :              const std::string& db_name,
     445                 :            :              std::string&& cmd,
     446                 :            :              const std::unordered_set< std::string >& my_hashes )
     447                 :            : // *****************************************************************************
     448                 :            : //  Remove documents from Xapian database
     449                 :            : //! \param[in] author Author of the database document
     450                 :            : //! \param[in] db_name Name of the Xapian database object
     451                 :            : //! \param[in,out] cmd Remove command
     452                 :            : //! \param[in] my_hashes Hashes to check for duplicates when removing documents
     453                 :            : //! \return Info string after remove database operation
     454                 :            : // *****************************************************************************
     455                 :            : {
     456                 :          3 :   trim( cmd );
     457 [ +  - ][ +  - ]:          3 :   MDEBUG( "db rm " + cmd );
         [ +  - ][ +  - ]
     458         [ -  + ]:          3 :   assert( not author.empty() );
     459         [ +  - ]:          3 :   if (not cmd.empty()) {
     460         [ +  - ]:          6 :     auto h = tokenize( cmd );
     461         [ +  - ]:          6 :     std::unordered_set< std::string > hashes_to_delete( begin(h), end(h) );
     462         [ +  - ]:          3 :     return db_rm_docs( author, db_name, hashes_to_delete, my_hashes );
     463                 :            :   }
     464         [ -  - ]:          0 :   return "unknown cmd";
     465                 :            : }
     466                 :            : 
     467                 :            : std::string
     468                 :         12 : piac::db_list( const std::string& db_name, std::string&& cmd )
     469                 :            : // *****************************************************************************
     470                 :            : //  List Xapian database
     471                 :            : //! \param[in] db_name Name of the Xapian database object
     472                 :            : //! \param[in,out] cmd List command
     473                 :            : //! \return List of items queried from database
     474                 :            : // *****************************************************************************
     475                 :            : {
     476                 :         12 :   trim( cmd );
     477 [ +  - ][ +  - ]:         12 :   MDEBUG( "db list " + cmd );
         [ +  - ][ +  - ]
     478                 :            : 
     479         [ +  + ]:         12 :   if (cmd.empty()) {
     480                 :            : 
     481         [ +  - ]:          2 :     auto docs = db_list_doc( db_name );
     482                 :          1 :     std::string result( "Number of documents: " +
     483 [ +  - ][ +  - ]:          2 :                         std::to_string( docs.size() ) + '\n' );
                 [ +  - ]
     484 [ +  + ][ +  - ]:          6 :     for (auto&& d : docs) result += std::move(d) + '\n';
                 [ +  - ]
     485                 :          1 :     result.pop_back();
     486                 :          1 :     return result;
     487                 :            : 
     488 [ +  - ][ +  - ]:         13 :   } else if (cmd[0]=='n' && cmd[1]=='u' && cmd[2]=='m' && cmd[3]=='d' &&
                 [ +  + ]
     489 [ +  + ][ +  - ]:         13 :              cmd[4]=='o' && cmd[5]=='c')
         [ +  - ][ +  + ]
     490                 :            :   {
     491                 :            : 
     492 [ +  - ][ +  - ]:          2 :     return "Number of documents: " + std::to_string( get_doccount( db_name ) );
                 [ +  - ]
     493                 :            : 
     494 [ +  - ][ +  - ]:         11 :   } else if (cmd[0]=='n' && cmd[1]=='u' && cmd[2]=='m' && cmd[3]=='u' &&
                 [ +  - ]
     495 [ +  + ][ +  - ]:         11 :              cmd[4]=='s' && cmd[5]=='r')
         [ +  - ][ +  + ]
     496                 :            :   {
     497                 :            : 
     498         [ +  - ]:          2 :     return "Number of users: " + std::to_string( db_list_numuser( db_name ) );
     499                 :            : 
     500 [ +  - ][ +  - ]:          9 :   } else if (cmd[0]=='h' && cmd[1]=='a' && cmd[2]=='s' && cmd[3]=='h') {
         [ +  - ][ +  - ]
                 [ +  - ]
     501                 :            : 
     502         [ +  - ]:          9 :     cmd.erase( 0, 5 );
     503         [ +  - ]:         18 :     auto hashes = db_list_hash( db_name, /* inhex = */ true );
     504                 :          9 :     std::string result( "Number of documents: " +
     505 [ +  - ][ +  - ]:         18 :                         std::to_string( hashes.size() ) + '\n' );
                 [ +  - ]
     506 [ +  + ][ +  - ]:         47 :     for (auto&& h : hashes) result += std::move(h) + '\n';
                 [ +  - ]
     507                 :          9 :     result.pop_back();
     508                 :          9 :     return result;
     509                 :            : 
     510                 :            :   }
     511                 :            : 
     512         [ -  - ]:          0 :   return "unknown cmd";
     513                 :            : }

Generated by: LCOV version 1.14