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.70.5_covts Lines: 76.3 % 118 90
Test Date: 2026-05-28 21:31:40 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 :     if ( result != U8_ERROR_NONE )
      99              :     {
     100            0 :         U8_LOG_ERROR( "Error at closing old and opening new database" );
     101              :     }
     102              : 
     103            0 :     U8_TRACE_END();
     104            0 : }
     105              : 
     106              : /* ================================ SEARCH_RESULT ================================ */
     107              : 
     108            2 : u8_error_t data_database_text_search_get_objects_by_text_fragment ( data_database_text_search_t *this_,
     109              :                                                                     const char *textfragment,
     110              :                                                                     data_search_result_iterator_t *io_search_result_iterator )
     111              : {
     112            2 :     U8_TRACE_BEGIN();
     113            2 :     assert( NULL != io_search_result_iterator );
     114            2 :     assert( NULL != textfragment );
     115            2 :     const unsigned int text_len = utf8string_get_length( textfragment );
     116            2 :     u8_error_t result = U8_ERROR_NONE;
     117              : 
     118              :     /* escape-encode textfragment */
     119              :     universal_memory_output_stream_t mem_out;
     120            2 :     universal_memory_output_stream_init( &mem_out,
     121            2 :                                          (*this_).temp_like_search_buf,
     122              :                                          sizeof( (*this_).temp_like_search_buf ),
     123              :                                          UNIVERSAL_MEMORY_OUTPUT_STREAM_0TERM_UTF8
     124              :                                        );
     125            2 :     const bool search_empty = ( 0 == text_len );
     126            2 :     if ( search_empty )
     127              :     {
     128              :         /* no wildcards and no escaping if search string is empty */
     129              :     }
     130              :     else
     131              :     {
     132            2 :         result |= universal_memory_output_stream_write( &mem_out,
     133              :                                                         &DATA_DATABASE_TEXT_SEARCH_SQL_LIKE_DELIMITER,
     134              :                                                         sizeof(DATA_DATABASE_TEXT_SEARCH_SQL_LIKE_DELIMITER)
     135              :                                                       );
     136              :         {
     137            2 :             universal_output_stream_t* stream_out = universal_memory_output_stream_get_output_stream( &mem_out );
     138              :             universal_escaping_output_stream_t esc_out;
     139            2 :             universal_escaping_output_stream_init( &esc_out, &DATA_DATABASE_TEXT_SEARCH_SQL_ENCODE, stream_out );
     140            2 :             result |= universal_escaping_output_stream_write( &esc_out, textfragment, text_len );
     141            2 :             result |= universal_escaping_output_stream_flush( &esc_out );
     142            2 :             result |= universal_escaping_output_stream_destroy( &esc_out );
     143              :         }
     144            2 :         result |= universal_memory_output_stream_write( &mem_out,
     145              :                                                         &DATA_DATABASE_TEXT_SEARCH_SQL_LIKE_DELIMITER,
     146              :                                                         sizeof(DATA_DATABASE_TEXT_SEARCH_SQL_LIKE_DELIMITER)
     147              :                                                       );
     148              :     }
     149            2 :     result |= universal_memory_output_stream_destroy( &mem_out );
     150              : 
     151            2 :     U8_TRACE_INFO_STR( "LIKE SEARCH:", (*this_).temp_like_search_buf );
     152            2 :     if ( result != U8_ERROR_NONE )
     153              :     {
     154            0 :         U8_LOG_WARNING_STR( "error at escaping the search string", textfragment );
     155              :     }
     156              :     else
     157              :     {
     158              :         /* search for the prepared pattern. In case of empty, search for a non-existing pattern in the type fields */
     159            2 :         const char *const search_name = search_empty ? "" : (*this_).temp_like_search_buf;
     160            2 :         const char *const search_type = search_empty ? "\n" : (*this_).temp_like_search_buf;
     161            2 :         const char *const search_descr = search_empty ? "" : (*this_).temp_like_search_buf;
     162              : 
     163            2 :         if ( (*this_).is_open )
     164              :         {
     165            2 :             sqlite3_stmt *const prepared_statement_diag = (*this_).statement_diagram_ids_by_textfragment;
     166            2 :             result |= data_database_text_search_private_bind_three_texts_to_statement( this_,
     167              :                                                                                        prepared_statement_diag,
     168              :                                                                                        search_name,
     169              :                                                                                        search_type,
     170              :                                                                                        search_descr
     171              :                                                                                      );
     172              : 
     173            2 :             sqlite3_stmt *const prepared_statement_class = (*this_).statement_classifier_ids_by_textfragment;
     174            2 :             result |= data_database_text_search_private_bind_three_texts_to_statement( this_,
     175              :                                                                                        prepared_statement_class,
     176              :                                                                                        search_name,
     177              :                                                                                        search_type,
     178              :                                                                                        search_descr
     179              :                                                                                      );
     180              : 
     181            2 :             sqlite3_stmt *const prepared_statement_feat = (*this_).statement_feature_ids_by_textfragment;
     182            2 :             result |= data_database_text_search_private_bind_three_texts_to_statement( this_,
     183              :                                                                                        prepared_statement_feat,
     184              :                                                                                        search_name, /* key */
     185              :                                                                                        search_type, /* value */
     186              :                                                                                        search_descr
     187              :                                                                                      );
     188              : 
     189            2 :             sqlite3_stmt *const prepared_statement_rel = (*this_).statement_relationship_ids_by_textfragment;
     190            2 :             result |= data_database_text_search_private_bind_three_texts_to_statement( this_,
     191              :                                                                                        prepared_statement_rel,
     192              :                                                                                        search_name,
     193              :                                                                                        search_type,
     194              :                                                                                        search_descr
     195              :                                                                                      );
     196              : 
     197              :             data_database_borrowed_stmt_t sql_statement_diag;
     198            2 :             data_database_borrowed_stmt_init( &sql_statement_diag,
     199              :                                               (*this_).database,
     200              :                                               prepared_statement_diag,
     201              :                                               &((*this_).statement_diagram_borrowed)
     202              :                                             );
     203              :             data_database_borrowed_stmt_t sql_statement_class;
     204            2 :             data_database_borrowed_stmt_init( &sql_statement_class,
     205              :                                               (*this_).database,
     206              :                                               prepared_statement_class,
     207              :                                               &((*this_).statement_classifier_borrowed)
     208              :                                             );
     209              :             data_database_borrowed_stmt_t sql_statement_feat;
     210            2 :             data_database_borrowed_stmt_init( &sql_statement_feat,
     211              :                                               (*this_).database,
     212              :                                               prepared_statement_feat,
     213              :                                               &((*this_).statement_feature_borrowed)
     214              :                                             );
     215              :             data_database_borrowed_stmt_t sql_statement_rel;
     216            2 :             data_database_borrowed_stmt_init( &sql_statement_rel,
     217              :                                               (*this_).database,
     218              :                                               prepared_statement_rel,
     219              :                                               &((*this_).statement_relationship_borrowed)
     220              :                                             );
     221            2 :             result |= data_search_result_iterator_reinit( io_search_result_iterator,
     222              :                                                           sql_statement_diag,
     223              :                                                           sql_statement_class,
     224              :                                                           sql_statement_feat,
     225              :                                                           sql_statement_rel
     226              :                                                         );
     227              :             /* do not destroy sql_statement_xxx; the object is transferred to the iterator and consumed there. */
     228              :         }
     229              :         else
     230              :         {
     231            0 :             result |= U8_ERROR_NO_DB;
     232            0 :             U8_TRACE_INFO( "Database not open, cannot request data." );
     233              :         }
     234              :     }
     235              : 
     236            2 :     U8_TRACE_END_ERR( result );
     237            2 :     return result;
     238              : }
     239              : 
     240              : /* ================================ private ================================ */
     241              : 
     242            2 : u8_error_t data_database_text_search_private_open( data_database_text_search_t *this_ )
     243              : {
     244            2 :     U8_TRACE_BEGIN();
     245            2 :     u8_error_t result = U8_ERROR_NONE;
     246              : 
     247            2 :     if ( ! (*this_).is_open )
     248              :     {
     249            2 :         result |= data_database_prepare_statement( (*this_).database,
     250              :                                                    DATA_SEARCH_RESULT_ITERATOR_SELECT_DIAGRAM_BY_TEXTFRAGMENT,
     251              :                                                    DATA_DATABASE_SQL_LENGTH_AUTO_DETECT,
     252              :                                                    &((*this_).statement_diagram_ids_by_textfragment)
     253              :                                                  );
     254            2 :         (*this_).statement_diagram_borrowed = false;
     255            2 :         result |= data_database_prepare_statement( (*this_).database,
     256              :                                                    DATA_SEARCH_RESULT_ITERATOR_SELECT_CLASSIFIER_BY_TEXTFRAGMENT,
     257              :                                                    DATA_DATABASE_SQL_LENGTH_AUTO_DETECT,
     258              :                                                    &((*this_).statement_classifier_ids_by_textfragment)
     259              :                                                  );
     260            2 :         (*this_).statement_classifier_borrowed = false;
     261            2 :         result |= data_database_prepare_statement( (*this_).database,
     262              :                                                    DATA_SEARCH_RESULT_ITERATOR_SELECT_FEATURE_BY_TEXTFRAGMENT,
     263              :                                                    DATA_DATABASE_SQL_LENGTH_AUTO_DETECT,
     264              :                                                    &((*this_).statement_feature_ids_by_textfragment)
     265              :                                                  );
     266            2 :         (*this_).statement_feature_borrowed = false;
     267            2 :         result |= data_database_prepare_statement( (*this_).database,
     268              :                                                    DATA_SEARCH_RESULT_ITERATOR_SELECT_RELATIONSHIP_BY_TEXTFRAGMENT,
     269              :                                                    DATA_DATABASE_SQL_LENGTH_AUTO_DETECT,
     270              :                                                    &((*this_).statement_relationship_ids_by_textfragment)
     271              :                                                  );
     272            2 :         (*this_).statement_relationship_borrowed = false;
     273              : 
     274            2 :         (*this_).is_open = true;
     275              :     }
     276              :     else
     277              :     {
     278            0 :         result |= U8_ERROR_INVALID_REQUEST;
     279            0 :         U8_LOG_WARNING( "Database is already open." );
     280              :     }
     281              : 
     282            2 :     U8_TRACE_END_ERR(result);
     283            2 :     return result;
     284              : }
     285              : 
     286            2 : u8_error_t data_database_text_search_private_close( data_database_text_search_t *this_ )
     287              : {
     288            2 :     U8_TRACE_BEGIN();
     289            2 :     u8_error_t result = U8_ERROR_NONE;
     290              : 
     291            2 :     if ( (*this_).is_open )
     292              :     {
     293            2 :         assert( (*this_).statement_relationship_borrowed == false );
     294            2 :         result |= data_database_finalize_statement( (*this_).database, (*this_).statement_relationship_ids_by_textfragment );
     295            2 :         assert( (*this_).statement_feature_borrowed == false );
     296            2 :         result |= data_database_finalize_statement( (*this_).database, (*this_).statement_feature_ids_by_textfragment );
     297            2 :         assert( (*this_).statement_classifier_borrowed == false );
     298            2 :         result |= data_database_finalize_statement( (*this_).database, (*this_).statement_classifier_ids_by_textfragment );
     299            2 :         assert( (*this_).statement_diagram_borrowed == false );
     300            2 :         result |= data_database_finalize_statement( (*this_).database, (*this_).statement_diagram_ids_by_textfragment );
     301              : 
     302            2 :         (*this_).is_open = false;
     303              :     }
     304              :     else
     305              :     {
     306            0 :         result |= U8_ERROR_INVALID_REQUEST;
     307            0 :         U8_LOG_WARNING( "Database was not open." );
     308              :     }
     309              : 
     310            2 :     U8_TRACE_END_ERR(result);
     311            2 :     return result;
     312              : }
     313              : 
     314              : 
     315              : /*
     316              : Copyright 2020-2026 Andreas Warnke
     317              : 
     318              : Licensed under the Apache License, Version 2.0 (the "License");
     319              : you may not use this file except in compliance with the License.
     320              : You may obtain a copy of the License at
     321              : 
     322              :     http://www.apache.org/licenses/LICENSE-2.0
     323              : 
     324              : Unless required by applicable law or agreed to in writing, software
     325              : distributed under the License is distributed on an "AS IS" BASIS,
     326              : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     327              : See the License for the specific language governing permissions and
     328              : limitations under the License.
     329              : */
        

Generated by: LCOV version 2.0-1