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 : */
|