Line data Source code
1 : /* File: data_database_head.c; Copyright and License: see below */
2 :
3 : #include "storage/data_database_head.h"
4 : #include "entity/data_id.h"
5 : #include "u8/u8_trace.h"
6 : #include "u8/u8_log.h"
7 : #include "utf8stringbuf/utf8string.h"
8 : #include "utf8stringbuf/utf8stringview.h"
9 : #include <assert.h>
10 :
11 : /*!
12 : * \brief value separator string constant to insert a diagram or classifier or other table-row
13 : */
14 : static const char *const DATA_DATABASE_HEAD_INSERT_VALUE_SEPARATOR = ",";
15 :
16 : /*!
17 : * \brief string start marker string constant to insert/update a diagram
18 : */
19 : static const char *const DATA_DATABASE_HEAD_STRING_VALUE_START = "\'";
20 :
21 : /*!
22 : * \brief string end marker string constant to insert/update a diagram
23 : */
24 : static const char *const DATA_DATABASE_HEAD_STRING_VALUE_END = "\'";
25 :
26 : /*!
27 : * \brief translation table to encode strings for usage in string literals
28 : *
29 : * Note: This table is not suitable for searches using the LIKE operator because _ and % are not handled.
30 : */
31 : const char *const (DATA_DATABASE_HEAD_SQL_ENCODE[][2]) = {
32 : { "'", "''" },
33 : { NULL, NULL }
34 : };
35 :
36 7 : void data_database_head_init ( data_database_head_t *this_, data_database_t *database )
37 : {
38 7 : U8_TRACE_BEGIN();
39 :
40 7 : (*this_).database = database;
41 :
42 : /* initialize a memory output stream */
43 7 : universal_memory_output_stream_init( &((*this_).plain_out),
44 7 : &((*this_).private_sql_buffer),
45 : sizeof((*this_).private_sql_buffer),
46 : UNIVERSAL_MEMORY_OUTPUT_STREAM_0TERM_UTF8
47 : );
48 : universal_output_stream_t *const plain_output
49 7 : = universal_memory_output_stream_get_output_stream( &((*this_).plain_out) );
50 :
51 7 : utf8stream_writer_init( &((*this_).plain), plain_output );
52 :
53 : /* initialize an sql escaped output stream */
54 7 : universal_escaping_output_stream_init( &((*this_).escaped_out),
55 : &DATA_DATABASE_HEAD_SQL_ENCODE,
56 : plain_output
57 : );
58 : universal_output_stream_t *const escaped_output
59 7 : = universal_escaping_output_stream_get_output_stream( &((*this_).escaped_out) );
60 :
61 7 : utf8stream_writer_init( &((*this_).escaped), escaped_output );
62 :
63 7 : U8_TRACE_END();
64 7 : }
65 :
66 7 : void data_database_head_destroy ( data_database_head_t *this_ )
67 : {
68 7 : U8_TRACE_BEGIN();
69 :
70 : /* de-initialize an sql escaped output stream */
71 7 : utf8stream_writer_destroy( &((*this_).escaped) );
72 7 : universal_escaping_output_stream_destroy( &((*this_).escaped_out) );
73 :
74 : /* de-initialize an output stream */
75 7 : utf8stream_writer_destroy( &((*this_).plain) );
76 7 : universal_memory_output_stream_destroy( &((*this_).plain_out) );
77 :
78 7 : (*this_).database = NULL;
79 :
80 7 : U8_TRACE_END();
81 7 : }
82 :
83 : /*!
84 : * \brief prefix search statement to find a head value by id
85 : */
86 : static const char *const DATA_DATABASE_HEAD_SELECT_HEAD_BY_ID_PREFIX =
87 : "SELECT id,key,value FROM head WHERE id=";
88 :
89 : /*!
90 : * \brief postfix search statement to find a head value by id
91 : */
92 : static const char *const DATA_DATABASE_HEAD_SELECT_HEAD_BY_ID_POSTFIX = ";";
93 :
94 5 : u8_error_t data_database_head_read_value_by_id ( data_database_head_t *this_, data_row_id_t obj_id, data_head_t *out_head )
95 : {
96 5 : U8_TRACE_BEGIN();
97 5 : assert( out_head != NULL );
98 5 : u8_error_t result = U8_ERROR_NONE;
99 :
100 : /* create an sql command */
101 : {
102 5 : result |= universal_memory_output_stream_reset( &((*this_).plain_out) );
103 :
104 5 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_SELECT_HEAD_BY_ID_PREFIX );
105 5 : result |= utf8stream_writer_write_int( &((*this_).plain), obj_id );
106 5 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_SELECT_HEAD_BY_ID_POSTFIX );
107 :
108 5 : result |= utf8stream_writer_flush( &((*this_).plain) ); /* enforces 0-termination on (*this_).plain_out */
109 : }
110 :
111 5 : if ( result == U8_ERROR_NONE )
112 : {
113 5 : const char *const sql_cmd = &((*this_).private_sql_buffer[0]);
114 : sqlite3_stmt *prepared_statement;
115 : int sqlite_err;
116 :
117 5 : result |= data_database_prepare_statement( (*this_).database,
118 : sql_cmd,
119 5 : utf8string_get_length( sql_cmd ) + sizeof( char ),
120 : &prepared_statement
121 : );
122 :
123 5 : U8_TRACE_INFO( "sqlite3_step()" );
124 5 : sqlite_err = sqlite3_step( prepared_statement );
125 5 : if ( SQLITE_ROW != sqlite_err )
126 : {
127 : /* Do not log this incident, the caller may not expect to find a row. */
128 1 : U8_TRACE_INFO_INT( "sqlite3_step did not find a row for id", obj_id );
129 1 : result |= U8_ERROR_NOT_FOUND;
130 : }
131 :
132 5 : if ( SQLITE_ROW == sqlite_err )
133 : {
134 4 : result |= data_head_init( out_head,
135 4 : sqlite3_column_int64( prepared_statement, 0 ),
136 4 : (const char*) sqlite3_column_text( prepared_statement, 1 ),
137 4 : (const char*) sqlite3_column_text( prepared_statement, 2 )
138 : );
139 :
140 4 : data_head_trace( out_head );
141 :
142 4 : sqlite_err = sqlite3_step( prepared_statement );
143 4 : if ( SQLITE_DONE != sqlite_err )
144 : {
145 0 : U8_LOG_ERROR_INT( "sqlite3_step not done yet:", sqlite_err );
146 0 : result |= U8_ERROR_DB_STRUCTURE;
147 : }
148 : }
149 :
150 5 : result |= data_database_finalize_statement( (*this_).database, prepared_statement );
151 : }
152 :
153 5 : U8_TRACE_END_ERR( result );
154 5 : return( result );
155 : }
156 :
157 : /*!
158 : * \brief prefix search statement to find a head value by key
159 : */
160 : static const char *const DATA_DATABASE_HEAD_SELECT_HEAD_BY_KEY_PREFIX =
161 : "SELECT id,key,value FROM head WHERE key=";
162 :
163 : /*!
164 : * \brief postfix search statement to find a head value by key
165 : */
166 : static const char *const DATA_DATABASE_HEAD_SELECT_HEAD_BY_KEY_POSTFIX = ";";
167 :
168 6 : u8_error_t data_database_head_read_value_by_key ( data_database_head_t *this_, const char *key, data_head_t *out_head )
169 : {
170 6 : U8_TRACE_BEGIN();
171 6 : assert( key != NULL );
172 6 : assert( out_head != NULL );
173 6 : u8_error_t result = U8_ERROR_NONE;
174 :
175 : /* create an sql command */
176 : {
177 6 : result |= universal_memory_output_stream_reset( &((*this_).plain_out) );
178 :
179 6 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_SELECT_HEAD_BY_KEY_PREFIX );
180 6 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_STRING_VALUE_START );
181 6 : result |= utf8stream_writer_flush( &((*this_).plain) );
182 6 : result |= utf8stream_writer_write_str( &((*this_).escaped), key );
183 6 : result |= utf8stream_writer_flush( &((*this_).escaped) );
184 6 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_STRING_VALUE_END );
185 6 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_SELECT_HEAD_BY_KEY_POSTFIX );
186 :
187 6 : result |= utf8stream_writer_flush( &((*this_).plain) ); /* enforces 0-termination on (*this_).plain_out */
188 : }
189 :
190 6 : if ( result == U8_ERROR_NONE )
191 : {
192 6 : const char *const sql_cmd = &((*this_).private_sql_buffer[0]);
193 : sqlite3_stmt *prepared_statement;
194 : int sqlite_err;
195 :
196 6 : result |= data_database_prepare_statement( (*this_).database,
197 : sql_cmd,
198 6 : utf8string_get_length( sql_cmd ) + sizeof( char ),
199 : &prepared_statement
200 : );
201 :
202 6 : U8_TRACE_INFO( "sqlite3_step()" );
203 6 : sqlite_err = sqlite3_step( prepared_statement );
204 6 : if ( SQLITE_ROW != sqlite_err )
205 : {
206 : /* Do not log this incident, the caller may not expect to find a row. */
207 4 : U8_TRACE_INFO_STR( "sqlite3_step did not find a row for key", key );
208 4 : result |= U8_ERROR_NOT_FOUND;
209 : }
210 :
211 6 : if ( SQLITE_ROW == sqlite_err )
212 : {
213 2 : result |= data_head_init( out_head,
214 2 : sqlite3_column_int64( prepared_statement, 0 ),
215 2 : (const char*) sqlite3_column_text( prepared_statement, 1 ),
216 2 : (const char*) sqlite3_column_text( prepared_statement, 2 )
217 : );
218 :
219 2 : data_head_trace( out_head );
220 :
221 2 : sqlite_err = sqlite3_step( prepared_statement );
222 2 : if ( SQLITE_DONE != sqlite_err )
223 : {
224 0 : U8_LOG_ERROR_INT( "sqlite3_step not done yet:", sqlite_err );
225 0 : result |= U8_ERROR_DB_STRUCTURE;
226 : }
227 : }
228 :
229 6 : result |= data_database_finalize_statement( (*this_).database, prepared_statement );
230 : }
231 :
232 6 : U8_TRACE_END_ERR( result );
233 6 : return( result );
234 : }
235 :
236 : /*!
237 : * \brief prefix string constant to insert a head value
238 : */
239 : static const char *const DATA_DATABASE_HEAD_INSERT_HEAD_PREFIX =
240 : "INSERT INTO head (key,value) VALUES (";
241 :
242 : /*!
243 : * \brief postfix string constant to insert a head value
244 : */
245 : static const char *const DATA_DATABASE_HEAD_INSERT_HEAD_POSTFIX = ");";
246 :
247 5 : u8_error_t data_database_head_create_value ( data_database_head_t *this_, const data_head_t *head, data_row_id_t* out_new_id )
248 : {
249 5 : U8_TRACE_BEGIN();
250 5 : assert( head != NULL );
251 5 : u8_error_t result = U8_ERROR_NONE;
252 :
253 : /* create an sql command */
254 : {
255 5 : result |= universal_memory_output_stream_reset( &((*this_).plain_out) );
256 :
257 5 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_INSERT_HEAD_PREFIX );
258 5 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_STRING_VALUE_START );
259 5 : result |= utf8stream_writer_flush( &((*this_).plain) );
260 5 : utf8string_t *const key = data_head_get_key_const( head );
261 5 : result |= utf8stream_writer_write_str( &((*this_).escaped), key );
262 5 : result |= utf8stream_writer_flush( &((*this_).escaped) );
263 5 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_STRING_VALUE_END );
264 5 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_INSERT_VALUE_SEPARATOR );
265 5 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_STRING_VALUE_START );
266 5 : result |= utf8stream_writer_flush( &((*this_).plain) );
267 5 : utf8string_t *const value = data_head_get_value_const( head );
268 5 : result |= utf8stream_writer_write_str( &((*this_).escaped), value );
269 5 : result |= utf8stream_writer_flush( &((*this_).escaped) );
270 5 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_STRING_VALUE_END );
271 5 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_INSERT_HEAD_POSTFIX );
272 :
273 5 : result |= utf8stream_writer_flush( &((*this_).plain) ); /* enforces 0-termination on (*this_).plain_out */
274 : }
275 :
276 5 : if ( result == U8_ERROR_NONE )
277 : {
278 5 : const char *const sql_cmd = &((*this_).private_sql_buffer[0]);
279 5 : data_row_id_t new_id = DATA_ROW_ID_VOID;
280 :
281 5 : result |= data_database_transaction_begin ( (*this_).database );
282 5 : result |= data_database_in_transaction_create( (*this_).database, sql_cmd, &new_id );
283 5 : result |= data_database_transaction_commit ( (*this_).database );
284 :
285 5 : U8_LOG_EVENT_INT( "sqlite3_exec: INSERT INTO head ... ->", new_id ); /* do not log confidential information, only id */
286 5 : if ( NULL != out_new_id )
287 : {
288 5 : *out_new_id = new_id;
289 : }
290 : }
291 :
292 5 : U8_TRACE_END_ERR( result );
293 5 : return( result );
294 : }
295 :
296 : /*!
297 : * \brief prefix string constant to delete a head value
298 : */
299 : static const char *const ATA_DATABASE_HEAD_DELETE_HEAD_PREFIX =
300 : "DELETE FROM head WHERE (id=";
301 :
302 : /*!
303 : * \brief postfix string constant to delete a head value
304 : */
305 : static const char *const DATA_DATABASE_HEAD_DELETE_HEAD_POSTFIX = ");";
306 :
307 3 : u8_error_t data_database_head_delete_value ( data_database_head_t *this_, data_row_id_t obj_id, data_head_t *out_old_head )
308 : {
309 3 : U8_TRACE_BEGIN();
310 3 : u8_error_t result = U8_ERROR_NONE;
311 :
312 3 : result |= data_database_transaction_begin ( (*this_).database );
313 : /* Note: out_old_head is NULL if old data shall not be returned */
314 3 : if ( NULL != out_old_head )
315 : {
316 2 : result |= data_database_head_read_value_by_id ( this_, obj_id, out_old_head );
317 : }
318 :
319 : /* create an sql command AFTER(!) reading the old value, same stringbuffer is used. */
320 : {
321 3 : result |= universal_memory_output_stream_reset( &((*this_).plain_out) );
322 :
323 3 : result |= utf8stream_writer_write_str( &((*this_).plain), ATA_DATABASE_HEAD_DELETE_HEAD_PREFIX );
324 3 : result |= utf8stream_writer_write_int( &((*this_).plain), obj_id );
325 3 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_DELETE_HEAD_POSTFIX );
326 :
327 3 : result |= utf8stream_writer_flush( &((*this_).plain) ); /* enforces 0-termination on (*this_).plain_out */
328 : }
329 :
330 3 : if ( result == U8_ERROR_NONE )
331 : {
332 2 : const char *const sql_cmd = &((*this_).private_sql_buffer[0]);
333 :
334 2 : result |= data_database_in_transaction_execute( (*this_).database, sql_cmd );
335 2 : U8_LOG_EVENT_INT( "sqlite3_exec: DELETE FROM head ... ->", obj_id ); /* do not log confidential information, only id */
336 : }
337 :
338 3 : result |= data_database_transaction_commit ( (*this_).database );
339 :
340 3 : U8_TRACE_END_ERR( result );
341 3 : return( result );
342 : }
343 :
344 : /*!
345 : * \brief prefix string constant to update a head value
346 : */
347 : static const char *DATA_DATABASE_HEAD_UPDATE_HEAD_PREFIX = "UPDATE head SET value=";
348 :
349 : /*!
350 : * \brief infix string constant to update a head value
351 : */
352 : static const char *DATA_DATABASE_HEAD_UPDATE_HEAD_INFIX = " WHERE id=";
353 :
354 : /*!
355 : * \brief postfix string constant to update a head value
356 : */
357 : static const char *DATA_DATABASE_HEAD_UPDATE_HEAD_POSTFIX = ";";
358 :
359 2 : u8_error_t data_database_head_update_value ( data_database_head_t *this_, data_row_id_t head_id, const char* new_head_value, data_head_t *out_old_head )
360 : {
361 2 : U8_TRACE_BEGIN();
362 2 : assert( new_head_value != NULL );
363 2 : u8_error_t result = U8_ERROR_NONE;
364 :
365 2 : result |= data_database_transaction_begin ( (*this_).database );
366 : /* Note: out_old_head is NULL if old data shall not be returned */
367 2 : if ( NULL != out_old_head )
368 : {
369 1 : result |= data_database_head_read_value_by_id ( this_, head_id, out_old_head );
370 : }
371 :
372 : /* create an sql command AFTER(!) reading the old value, same stringbuffer is used. */
373 : {
374 2 : result |= universal_memory_output_stream_reset( &((*this_).plain_out) );
375 :
376 2 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_UPDATE_HEAD_PREFIX );
377 2 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_STRING_VALUE_START );
378 2 : result |= utf8stream_writer_flush( &((*this_).plain) );
379 2 : result |= utf8stream_writer_write_str( &((*this_).escaped), new_head_value );
380 2 : result |= utf8stream_writer_flush( &((*this_).escaped) );
381 2 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_STRING_VALUE_END );
382 2 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_UPDATE_HEAD_INFIX );
383 2 : result |= utf8stream_writer_write_int( &((*this_).plain), head_id );
384 2 : result |= utf8stream_writer_write_str( &((*this_).plain), DATA_DATABASE_HEAD_UPDATE_HEAD_POSTFIX );
385 :
386 2 : result |= utf8stream_writer_flush( &((*this_).plain) ); /* enforces 0-termination on (*this_).plain_out */
387 : }
388 :
389 2 : if ( result == U8_ERROR_NONE )
390 : {
391 2 : const char *const sql_cmd = &((*this_).private_sql_buffer[0]);
392 :
393 2 : result |= data_database_in_transaction_execute( (*this_).database, sql_cmd );
394 2 : U8_LOG_EVENT_INT( "sqlite3_exec: UPDATE head ... ->", head_id ); /* do not log confidential information, only id */
395 : }
396 :
397 2 : result |= data_database_transaction_commit ( (*this_).database );
398 :
399 2 : U8_TRACE_END_ERR( result );
400 2 : return( result );
401 : }
402 :
403 :
404 : /*
405 : Copyright 2024-2024 Andreas Warnke
406 :
407 : Licensed under the Apache License, Version 2.0 (the "License");
408 : you may not use this file except in compliance with the License.
409 : You may obtain a copy of the License at
410 :
411 : http://www.apache.org/licenses/LICENSE-2.0
412 :
413 : Unless required by applicable law or agreed to in writing, software
414 : distributed under the License is distributed on an "AS IS" BASIS,
415 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
416 : See the License for the specific language governing permissions and
417 : limitations under the License.
418 : */
|