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