LCOV - code coverage report
Current view: top level - data/source/storage - data_database_text_search.c (source / functions) Coverage Total Hit
Test: crystal-facet-uml_v1.67.0_covts Lines: 77.6 % 116 90
Test Date: 2025-11-06 17:22:08 Functions: 83.3 % 6 5

            Line data    Source code
       1              : /* File: data_database_text_search.c; Copyright and License: see below */
       2              : 
       3              : #include "storage/data_database_text_search.h"
       4              : #include "u8/u8_trace.h"
       5              : #include "u8/u8_log.h"
       6              : #include "utf8stringbuf/utf8stringbuf.h"
       7              : #include "u8stream/universal_output_stream.h"
       8              : #include "u8stream/universal_memory_output_stream.h"
       9              : #include "u8stream/universal_escaping_output_stream.h"
      10              : #include <sqlite3.h>
      11              : #include <assert.h>
      12              : 
      13              : /*!
      14              :  *  \brief translation table to encode strings for usage in LIKE search string literals
      15              :  */
      16              : static const char *const DATA_DATABASE_TEXT_SEARCH_SQL_ENCODE[][2] = {
      17              :     { "%", "\\%" },
      18              :     { "_", "\\_" },
      19              :     { "\\", "\\\\" },
      20              :     { NULL, NULL }
      21              : };
      22              : static const char DATA_DATABASE_TEXT_SEARCH_SQL_LIKE_DELIMITER = '%';
      23              : 
      24            2 : u8_error_t data_database_text_search_init( data_database_text_search_t *this_, data_database_t *database )
      25              : {
      26            2 :     U8_TRACE_BEGIN();
      27            2 :     assert( NULL != database );
      28            2 :     u8_error_t result = U8_ERROR_NONE;
      29              : 
      30            2 :     (*this_).database = database;
      31            2 :     (*this_).is_open = false;
      32              : 
      33            2 :     data_database_listener_init( &((*this_).me_as_listener), this_, (void (*)(void*,data_database_listener_signal_t)) &data_database_text_search_db_change_callback );
      34            2 :     data_database_add_db_listener( database, &((*this_).me_as_listener) );
      35              : 
      36            2 :     if ( data_database_is_open( database ) )
      37              :     {
      38              :         /* if the database is open, open also the reader */
      39            2 :         result |= data_database_text_search_private_open( this_ );
      40              :     }
      41              : 
      42            2 :     U8_TRACE_END_ERR(result);
      43            2 :     return result;
      44              : }
      45              : 
      46            2 : u8_error_t data_database_text_search_destroy( data_database_text_search_t *this_ )
      47              : {
      48            2 :     U8_TRACE_BEGIN();
      49            2 :     u8_error_t result = U8_ERROR_NONE;
      50              : 
      51            2 :     if ( (*this_).is_open )
      52              :     {
      53            2 :         result |= data_database_text_search_private_close( this_ );
      54              :     }
      55              : 
      56            2 :     data_database_remove_db_listener( (*this_).database, &((*this_).me_as_listener) );
      57              : 
      58            2 :     (*this_).database = NULL;
      59              : 
      60            2 :     U8_TRACE_END_ERR(result);
      61            2 :     return result;
      62              : }
      63              : 
      64            0 : void data_database_text_search_db_change_callback( data_database_text_search_t *this_, data_database_listener_signal_t signal_id )
      65              : {
      66            0 :     U8_TRACE_BEGIN();
      67            0 :     u8_error_t result = U8_ERROR_NONE;
      68              : 
      69            0 :     switch ( signal_id )
      70              :     {
      71            0 :         case DATA_DATABASE_LISTENER_SIGNAL_PREPARE_CLOSE:
      72              :         {
      73            0 :             U8_TRACE_INFO( "DATA_DATABASE_LISTENER_SIGNAL_PREPARE_CLOSE" );
      74            0 :             if ( (*this_).is_open )
      75              :             {
      76            0 :                 result |= data_database_text_search_private_close( this_ );
      77              :             }
      78              :         }
      79            0 :         break;
      80              : 
      81            0 :         case DATA_DATABASE_LISTENER_SIGNAL_DB_OPENED:
      82              :         {
      83            0 :             U8_TRACE_INFO( "DATA_DATABASE_LISTENER_SIGNAL_DB_OPENED" );
      84            0 :             if ( (*this_).is_open )
      85              :             {
      86            0 :                 result |= data_database_text_search_private_close( this_ );
      87              :             }
      88            0 :             result |= data_database_text_search_private_open( this_ );
      89              :         }
      90            0 :         break;
      91              : 
      92            0 :         default:
      93              :         {
      94            0 :             U8_LOG_ERROR( "unexpected data_database_listener_signal_t" );
      95              :         }
      96              :     }
      97              : 
      98            0 :     U8_TRACE_END();
      99            0 : }
     100              : 
     101              : /* ================================ SEARCH_RESULT ================================ */
     102              : 
     103            2 : u8_error_t data_database_text_search_get_objects_by_text_fragment ( data_database_text_search_t *this_,
     104              :                                                                     const char *textfragment,
     105              :                                                                     data_search_result_iterator_t *io_search_result_iterator )
     106              : {
     107            2 :     U8_TRACE_BEGIN();
     108            2 :     assert( NULL != io_search_result_iterator );
     109            2 :     assert( NULL != textfragment );
     110            2 :     const unsigned int text_len = utf8string_get_length( textfragment );
     111            2 :     u8_error_t result = U8_ERROR_NONE;
     112              : 
     113              :     /* escape-encode textfragment */
     114              :     universal_memory_output_stream_t mem_out;
     115            2 :     universal_memory_output_stream_init( &mem_out,
     116            2 :                                          (*this_).temp_like_search_buf,
     117              :                                          sizeof( (*this_).temp_like_search_buf ),
     118              :                                          UNIVERSAL_MEMORY_OUTPUT_STREAM_0TERM_UTF8
     119              :                                        );
     120            2 :     const bool search_empty = ( 0 == text_len );
     121            2 :     if ( search_empty )
     122              :     {
     123              :         /* no wildcards and no escaping if search string is empty */
     124              :     }
     125              :     else
     126              :     {
     127            2 :         result |= universal_memory_output_stream_write( &mem_out,
     128              :                                                         &DATA_DATABASE_TEXT_SEARCH_SQL_LIKE_DELIMITER,
     129              :                                                         sizeof(DATA_DATABASE_TEXT_SEARCH_SQL_LIKE_DELIMITER)
     130              :                                                       );
     131              :         {
     132            2 :             universal_output_stream_t* stream_out = universal_memory_output_stream_get_output_stream( &mem_out );
     133              :             universal_escaping_output_stream_t esc_out;
     134            2 :             universal_escaping_output_stream_init( &esc_out, &DATA_DATABASE_TEXT_SEARCH_SQL_ENCODE, stream_out );
     135            2 :             result |= universal_escaping_output_stream_write( &esc_out, textfragment, text_len );
     136            2 :             result |= universal_escaping_output_stream_flush( &esc_out );
     137            2 :             result |= universal_escaping_output_stream_destroy( &esc_out );
     138              :         }
     139            2 :         result |= universal_memory_output_stream_write( &mem_out,
     140              :                                                         &DATA_DATABASE_TEXT_SEARCH_SQL_LIKE_DELIMITER,
     141              :                                                         sizeof(DATA_DATABASE_TEXT_SEARCH_SQL_LIKE_DELIMITER)
     142              :                                                       );
     143              :     }
     144            2 :     result |= universal_memory_output_stream_destroy( &mem_out );
     145              : 
     146            2 :     U8_TRACE_INFO_STR( "LIKE SEARCH:", (*this_).temp_like_search_buf );
     147            2 :     if ( result != U8_ERROR_NONE )
     148              :     {
     149            0 :         U8_LOG_WARNING_STR( "error at escaping the search string", textfragment );
     150              :     }
     151              :     else
     152              :     {
     153              :         /* search for the prepared pattern. In case of empty, search for a non-existing pattern in the type fields */
     154            2 :         const char *const search_name = search_empty ? "" : (*this_).temp_like_search_buf;
     155            2 :         const char *const search_type = search_empty ? "\n" : (*this_).temp_like_search_buf;
     156            2 :         const char *const search_descr = search_empty ? "" : (*this_).temp_like_search_buf;
     157              : 
     158            2 :         if ( (*this_).is_open )
     159              :         {
     160            2 :             sqlite3_stmt *const prepared_statement_diag = (*this_).statement_diagram_ids_by_textfragment;
     161            2 :             result |= data_database_text_search_private_bind_three_texts_to_statement( this_,
     162              :                                                                                        prepared_statement_diag,
     163              :                                                                                        search_name,
     164              :                                                                                        search_type,
     165              :                                                                                        search_descr
     166              :                                                                                      );
     167              : 
     168            2 :             sqlite3_stmt *const prepared_statement_class = (*this_).statement_classifier_ids_by_textfragment;
     169            2 :             result |= data_database_text_search_private_bind_three_texts_to_statement( this_,
     170              :                                                                                        prepared_statement_class,
     171              :                                                                                        search_name,
     172              :                                                                                        search_type,
     173              :                                                                                        search_descr
     174              :                                                                                      );
     175              : 
     176            2 :             sqlite3_stmt *const prepared_statement_feat = (*this_).statement_feature_ids_by_textfragment;
     177            2 :             result |= data_database_text_search_private_bind_three_texts_to_statement( this_,
     178              :                                                                                        prepared_statement_feat,
     179              :                                                                                        search_name, /* key */
     180              :                                                                                        search_type, /* value */
     181              :                                                                                        search_descr
     182              :                                                                                      );
     183              : 
     184            2 :             sqlite3_stmt *const prepared_statement_rel = (*this_).statement_relationship_ids_by_textfragment;
     185            2 :             result |= data_database_text_search_private_bind_three_texts_to_statement( this_,
     186              :                                                                                        prepared_statement_rel,
     187              :                                                                                        search_name,
     188              :                                                                                        search_type,
     189              :                                                                                        search_descr
     190              :                                                                                      );
     191              : 
     192              :             data_database_borrowed_stmt_t sql_statement_diag;
     193            2 :             data_database_borrowed_stmt_init( &sql_statement_diag,
     194              :                                               (*this_).database,
     195              :                                               prepared_statement_diag,
     196              :                                               &((*this_).statement_diagram_borrowed)
     197              :                                             );
     198              :             data_database_borrowed_stmt_t sql_statement_class;
     199            2 :             data_database_borrowed_stmt_init( &sql_statement_class,
     200              :                                               (*this_).database,
     201              :                                               prepared_statement_class,
     202              :                                               &((*this_).statement_classifier_borrowed)
     203              :                                             );
     204              :             data_database_borrowed_stmt_t sql_statement_feat;
     205            2 :             data_database_borrowed_stmt_init( &sql_statement_feat,
     206              :                                               (*this_).database,
     207              :                                               prepared_statement_feat,
     208              :                                               &((*this_).statement_feature_borrowed)
     209              :                                             );
     210              :             data_database_borrowed_stmt_t sql_statement_rel;
     211            2 :             data_database_borrowed_stmt_init( &sql_statement_rel,
     212              :                                               (*this_).database,
     213              :                                               prepared_statement_rel,
     214              :                                               &((*this_).statement_relationship_borrowed)
     215              :                                             );
     216            2 :             result |= data_search_result_iterator_reinit( io_search_result_iterator,
     217              :                                                           sql_statement_diag,
     218              :                                                           sql_statement_class,
     219              :                                                           sql_statement_feat,
     220              :                                                           sql_statement_rel
     221              :                                                         );
     222              :             /* do not destroy sql_statement_xxx; the object is transferred to the iterator and consumed there. */
     223              :         }
     224              :         else
     225              :         {
     226            0 :             result |= U8_ERROR_NO_DB;
     227            0 :             U8_TRACE_INFO( "Database not open, cannot request data." );
     228              :         }
     229              :     }
     230              : 
     231            2 :     U8_TRACE_END_ERR( result );
     232            2 :     return result;
     233              : }
     234              : 
     235              : /* ================================ private ================================ */
     236              : 
     237            2 : u8_error_t data_database_text_search_private_open( data_database_text_search_t *this_ )
     238              : {
     239            2 :     U8_TRACE_BEGIN();
     240            2 :     u8_error_t result = U8_ERROR_NONE;
     241              : 
     242            2 :     if ( ! (*this_).is_open )
     243              :     {
     244            2 :         result |= data_database_prepare_statement( (*this_).database,
     245              :                                                    DATA_SEARCH_RESULT_ITERATOR_SELECT_DIAGRAM_BY_TEXTFRAGMENT,
     246              :                                                    DATA_DATABASE_SQL_LENGTH_AUTO_DETECT,
     247              :                                                    &((*this_).statement_diagram_ids_by_textfragment)
     248              :                                                  );
     249            2 :         (*this_).statement_diagram_borrowed = false;
     250            2 :         result |= data_database_prepare_statement( (*this_).database,
     251              :                                                    DATA_SEARCH_RESULT_ITERATOR_SELECT_CLASSIFIER_BY_TEXTFRAGMENT,
     252              :                                                    DATA_DATABASE_SQL_LENGTH_AUTO_DETECT,
     253              :                                                    &((*this_).statement_classifier_ids_by_textfragment)
     254              :                                                  );
     255            2 :         (*this_).statement_classifier_borrowed = false;
     256            2 :         result |= data_database_prepare_statement( (*this_).database,
     257              :                                                    DATA_SEARCH_RESULT_ITERATOR_SELECT_FEATURE_BY_TEXTFRAGMENT,
     258              :                                                    DATA_DATABASE_SQL_LENGTH_AUTO_DETECT,
     259              :                                                    &((*this_).statement_feature_ids_by_textfragment)
     260              :                                                  );
     261            2 :         (*this_).statement_feature_borrowed = false;
     262            2 :         result |= data_database_prepare_statement( (*this_).database,
     263              :                                                    DATA_SEARCH_RESULT_ITERATOR_SELECT_RELATIONSHIP_BY_TEXTFRAGMENT,
     264              :                                                    DATA_DATABASE_SQL_LENGTH_AUTO_DETECT,
     265              :                                                    &((*this_).statement_relationship_ids_by_textfragment)
     266              :                                                  );
     267            2 :         (*this_).statement_relationship_borrowed = false;
     268              : 
     269            2 :         (*this_).is_open = true;
     270              :     }
     271              :     else
     272              :     {
     273            0 :         result |= U8_ERROR_INVALID_REQUEST;
     274            0 :         U8_LOG_WARNING( "Database is already open." );
     275              :     }
     276              : 
     277            2 :     U8_TRACE_END_ERR(result);
     278            2 :     return result;
     279              : }
     280              : 
     281            2 : u8_error_t data_database_text_search_private_close( data_database_text_search_t *this_ )
     282              : {
     283            2 :     U8_TRACE_BEGIN();
     284            2 :     u8_error_t result = U8_ERROR_NONE;
     285              : 
     286            2 :     if ( (*this_).is_open )
     287              :     {
     288            2 :         assert( (*this_).statement_relationship_borrowed == false );
     289            2 :         result |= data_database_finalize_statement( (*this_).database, (*this_).statement_relationship_ids_by_textfragment );
     290            2 :         assert( (*this_).statement_feature_borrowed == false );
     291            2 :         result |= data_database_finalize_statement( (*this_).database, (*this_).statement_feature_ids_by_textfragment );
     292            2 :         assert( (*this_).statement_classifier_borrowed == false );
     293            2 :         result |= data_database_finalize_statement( (*this_).database, (*this_).statement_classifier_ids_by_textfragment );
     294            2 :         assert( (*this_).statement_diagram_borrowed == false );
     295            2 :         result |= data_database_finalize_statement( (*this_).database, (*this_).statement_diagram_ids_by_textfragment );
     296              : 
     297            2 :         (*this_).is_open = false;
     298              :     }
     299              :     else
     300              :     {
     301            0 :         result |= U8_ERROR_INVALID_REQUEST;
     302            0 :         U8_LOG_WARNING( "Database was not open." );
     303              :     }
     304              : 
     305            2 :     U8_TRACE_END_ERR(result);
     306            2 :     return result;
     307              : }
     308              : 
     309              : 
     310              : /*
     311              : Copyright 2020-2025 Andreas Warnke
     312              : 
     313              : Licensed under the Apache License, Version 2.0 (the "License");
     314              : you may not use this file except in compliance with the License.
     315              : You may obtain a copy of the License at
     316              : 
     317              :     http://www.apache.org/licenses/LICENSE-2.0
     318              : 
     319              : Unless required by applicable law or agreed to in writing, software
     320              : distributed under the License is distributed on an "AS IS" BASIS,
     321              : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     322              : See the License for the specific language governing permissions and
     323              : limitations under the License.
     324              : */
        

Generated by: LCOV version 2.0-1