LCOV - code coverage report
Current view: top level - data/source/storage - data_database_head.c (source / functions) Hit Total Coverage
Test: crystal-facet-uml_v1.57.0_covts Lines: 159 163 97.5 %
Date: 2024-04-07 11:14:42 Functions: 7 7 100.0 %

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

Generated by: LCOV version 1.16