LCOV - code coverage report
Current view: top level - data/source/storage - data_database.c (source / functions) Coverage Total Hit
Test: crystal-facet-uml_v1.63.2_covts Lines: 74.5 % 318 237
Test Date: 2025-05-01 10:10:14 Functions: 86.7 % 15 13

            Line data    Source code
       1              : /* File: data_database.c; Copyright and License: see below */
       2              : 
       3              : #include "storage/data_database.h"
       4              : #include "entity/data_id.h"
       5              : #include "entity/data_table.h"
       6              : #include "u8/u8_trace.h"
       7              : #include "u8/u8_log.h"
       8              : #include <assert.h>
       9              : 
      10              : const char DATA_DATABASE_SQLITE3_MAGIC[16]
      11              :     = {0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00 };
      12              : 
      13              : /*!
      14              :  *  \brief string constant to create an sql database table
      15              :  *
      16              :  *  This table contains head values
      17              :  */
      18              : static const char *DATA_DATABASE_CREATE_HEAD_TABLE =
      19              :     "CREATE TABLE IF NOT EXISTS head ( "
      20              :         "id INTEGER NOT NULL PRIMARY KEY ASC, "
      21              :         "key TEXT NOT NULL UNIQUE, "
      22              :         "value TEXT NOT NULL"
      23              :     ");";
      24              : 
      25              : /*!
      26              :  *  \brief string constant to create an sql database table
      27              :  *
      28              :  *  This table contains instances of classes, states, activities, interfaces (which are classifiers)
      29              :  *  and also packages.
      30              :  *  It does not contain relationships (even if they are classifiers) like generalizations, associations.
      31              :  *  \see http://www.omg.org/spec/UML/
      32              :  */
      33              : static const char *DATA_DATABASE_CREATE_CLASSIFIER_TABLE =
      34              :     "CREATE TABLE IF NOT EXISTS classifiers ( "
      35              :         "id INTEGER NOT NULL PRIMARY KEY ASC, "
      36              :         "main_type INTEGER NOT NULL, "
      37              :         "stereotype TEXT, "
      38              :         "name TEXT UNIQUE, "
      39              :         "description TEXT, "
      40              :         "x_order INTEGER, "
      41              :         "y_order INTEGER, "
      42              :         "list_order INTEGER, "
      43              :         "uuid TEXT NOT NULL DEFAULT \'\'"  /* DEFAULT needed in case a new DB is modified by an old program version */
      44              :     ");";
      45              : 
      46              : /*!
      47              :  *  \brief string constant to update an sql database table
      48              :  *
      49              :  *  The DEFAULT clause is needed to convert the existing records to the new format
      50              :  *  and in case a new database is modified by an old program version.
      51              :  *
      52              :  *  This command extends classifiers by a uuid field.
      53              :  *  \see http://sqlite.org/lang_altertable.html
      54              :  */
      55              : static const char *DATA_DATABASE_ALTER_CLASSIFIER_TABLE_UUID =
      56              :     "ALTER TABLE classifiers "
      57              :     "ADD COLUMN uuid TEXT NOT NULL DEFAULT \'\';";
      58              : 
      59              : #define DATA_DATABASE_CREATE_UUID \
      60              : "lower(hex( randomblob(4)) || '-' || hex( randomblob(2))"\
      61              : " || '-' || '4' || substr( hex( randomblob(2)), 2) || '-' || substr('89AB', 1 + (abs(random()) % 4) , 1) ||"\
      62              : " substr(hex(randomblob(2)), 2) || '-' || hex(randomblob(6)))"
      63              : 
      64              : /*!
      65              :  *  \brief string constant to update an sql database table
      66              :  *
      67              :  *  A uuid is generated and updated wherever missing.
      68              :  *  The subselect-WHERE clause is needed to generate for each row a different uuid.
      69              :  */
      70              : static const char *DATA_DATABASE_UPDATE_CLASSIFIER_UUID =
      71              :     "UPDATE classifiers SET uuid=(SELECT " DATA_DATABASE_CREATE_UUID " WHERE classifiers.id!=-1) WHERE uuid=\'\';";
      72              : 
      73              : /*!
      74              :  *  \brief string constant to create an sql database table
      75              :  *
      76              :  *  This table contains instances of generalizations, associations (which are relationships)
      77              :  *  \see http://www.omg.org/spec/UML/
      78              :  */
      79              : static const char *DATA_DATABASE_CREATE_RELATIONSHIP_TABLE =
      80              :     "CREATE TABLE IF NOT EXISTS relationships ( "
      81              :         "id INTEGER NOT NULL PRIMARY KEY ASC, "
      82              :         "main_type INTEGER NOT NULL, "
      83              :         "from_classifier_id INTEGER NOT NULL, "
      84              :         "to_classifier_id INTEGER NOT NULL, "
      85              :         "stereotype TEXT DEFAULT \'\', "  /* since 1.47.0, DEFAULT needed in case a new DB is modified by an old program version */
      86              :         "name TEXT, "
      87              :         "description TEXT, "
      88              :         "list_order INTEGER, "
      89              :         "from_feature_id INTEGER DEFAULT NULL, "  /* DEFAULT needed in case a new DB is modified by an old program version */
      90              :         "to_feature_id INTEGER DEFAULT NULL, "  /* DEFAULT needed in case a new DB is modified by an old program version */
      91              :         "uuid TEXT NOT NULL DEFAULT \'\', "  /* DEFAULT needed in case a new DB is modified by an old program version */
      92              :         "FOREIGN KEY(from_classifier_id) REFERENCES classifiers(id), "
      93              :         "FOREIGN KEY(to_classifier_id) REFERENCES classifiers(id), "
      94              :         "FOREIGN KEY(from_feature_id) REFERENCES features(id), "
      95              :         "FOREIGN KEY(to_feature_id) REFERENCES features(id) "
      96              :     ");";
      97              : 
      98              : /*!
      99              :  *  \brief string constant to update an sql database table
     100              :  *
     101              :  *  The DEFAULT clause is needed to convert the existing records to the new format
     102              :  *  and in case a new database is modified by an old program version.
     103              :  *
     104              :  *  This command extends relationships by a uuid field.
     105              :  */
     106              : static const char *DATA_DATABASE_ALTER_RELATIONSHIP_TABLE_UUID =
     107              :     "ALTER TABLE relationships "
     108              :     "ADD COLUMN uuid TEXT NOT NULL DEFAULT \'\';";
     109              : 
     110              : /*!
     111              :  *  \brief string constant to update an sql database table
     112              :  *
     113              :  *  A uuid is generated and updated wherever missing.
     114              :  *  The subselect-WHERE clause is needed to generate for each row a different uuid.
     115              :  */
     116              : static const char *DATA_DATABASE_UPDATE_RELATIONSHIP_UUID =
     117              :     "UPDATE relationships SET uuid=(SELECT " DATA_DATABASE_CREATE_UUID " WHERE relationships.id!=-1) WHERE uuid=\'\';";
     118              : 
     119              : /*!
     120              :  *  \brief string constant to update an sql database table
     121              :  *
     122              :  *  The DEFAULT clause is needed to convert the existing records to the new format
     123              :  *  and in case a new database is modified by an old program version.
     124              :  *
     125              :  *  This command extends relationships by a stereotype field.
     126              :  */
     127              : static const char *DATA_DATABASE_ALTER_RELATIONSHIP_TABLE_STEREOTYPE =
     128              :     "ALTER TABLE relationships "
     129              :     "ADD COLUMN stereotype TEXT DEFAULT \'\';";
     130              : 
     131              : /*!
     132              :  *  \brief string constant to create an sql database table
     133              :  *
     134              :  *  This table contains instances of attributes (which are properties which are features).
     135              :  *  \see http://www.omg.org/spec/UML/
     136              :  */
     137              : static const char *DATA_DATABASE_CREATE_FEATURE_TABLE =
     138              :     "CREATE TABLE IF NOT EXISTS features ( "
     139              :         "id INTEGER NOT NULL PRIMARY KEY ASC, "
     140              :         "main_type INTEGER NOT NULL, "
     141              :         "classifier_id INTEGER NOT NULL, "
     142              :         "key TEXT, "
     143              :         "value TEXT, "
     144              :         "description TEXT, "
     145              :         "list_order INTEGER, "
     146              :         "uuid TEXT NOT NULL DEFAULT \'\', "  /* DEFAULT needed in case a new DB is modified by an old program version */
     147              :         "FOREIGN KEY(classifier_id) REFERENCES classifiers(id) "
     148              :     ");";
     149              : 
     150              : /*!
     151              :  *  \brief string constant to update an sql database table
     152              :  *
     153              :  *  The DEFAULT clause is needed to convert the existing records to the new format
     154              :  *  and in case a new database is modified by an old program version.
     155              :  *
     156              :  *  This command extends featues by a uuid field.
     157              :  */
     158              : static const char *DATA_DATABASE_ALTER_FEATURE_TABLE_UUID =
     159              :     "ALTER TABLE features "
     160              :     "ADD COLUMN uuid TEXT NOT NULL DEFAULT \'\';";
     161              : 
     162              : /*!
     163              :  *  \brief string constant to update an sql database table
     164              :  *
     165              :  *  A uuid is generated and updated wherever missing.
     166              :  *  The subselect-WHERE clause is needed to generate for each row a different uuid.
     167              :  */
     168              : static const char *DATA_DATABASE_UPDATE_FEATURE_UUID =
     169              :     "UPDATE features SET uuid=(SELECT " DATA_DATABASE_CREATE_UUID " WHERE features.id!=-1) WHERE uuid=\'\';";
     170              : 
     171              : /*!
     172              :  *  \brief string constant to create an sql database table
     173              :  */
     174              : static const char *DATA_DATABASE_CREATE_DIAGRAM_TABLE =
     175              :     "CREATE TABLE IF NOT EXISTS diagrams ( "
     176              :         "id INTEGER NOT NULL PRIMARY KEY ASC, "
     177              :         "parent_id INTEGER, "  /* is NULL for the root diagram */
     178              :         "diagram_type INTEGER NOT NULL, "
     179              :         "stereotype TEXT DEFAULT \'\', "  /* since 1.47.0, DEFAULT needed in case a new DB is modified by an old program version */
     180              :         "name TEXT, "
     181              :         "description TEXT, "
     182              :         "list_order INTEGER, "
     183              :         "display_flags INTEGER NOT NULL DEFAULT 0, "  /* DEFAULT needed in case a new DB is modified by an old program version */
     184              :         "uuid TEXT NOT NULL DEFAULT \'\', "  /* DEFAULT needed in case a new DB is modified by an old program version */
     185              :         "FOREIGN KEY(parent_id) REFERENCES diagrams(id) "
     186              :     ");";
     187              : 
     188              : /*!
     189              :  *  \brief string constant to update an sql database table
     190              :  *
     191              :  *  The DEFAULT clause is needed to convert the existing records to the new format
     192              :  *  and in case a new database is modified by an old program version.
     193              :  *
     194              :  *  This command extends diagrams by display_flags field.
     195              :  */
     196              : static const char *DATA_DATABASE_ALTER_DIAGRAM_TABLE_1 =
     197              :     "ALTER TABLE diagrams "
     198              :     "ADD COLUMN display_flags INTEGER NOT NULL DEFAULT 0;";
     199              : 
     200              : /*!
     201              :  *  \brief string constant to update an sql database table
     202              :  *
     203              :  *  The DEFAULT clause is needed to convert the existing records to the new format
     204              :  *  and in case a new database is modified by an old program version.
     205              :  *
     206              :  *  This command extends diagrams by a uuid field.
     207              :  */
     208              : static const char *DATA_DATABASE_ALTER_DIAGRAM_TABLE_UUID =
     209              :     "ALTER TABLE diagrams "
     210              :     "ADD COLUMN uuid TEXT NOT NULL DEFAULT \'\';";
     211              : 
     212              : /*!
     213              :  *  \brief string constant to update an sql database table
     214              :  *
     215              :  *  A uuid is generated and updated wherever missing.
     216              :  *  The subselect-WHERE clause is needed to generate for each row a different uuid.
     217              :  */
     218              : static const char *DATA_DATABASE_UPDATE_DIAGRAM_UUID =
     219              :     "UPDATE diagrams SET uuid=(SELECT " DATA_DATABASE_CREATE_UUID " WHERE diagrams.id!=-1) WHERE uuid=\'\';";
     220              : 
     221              : /*!
     222              :  *  \brief string constant to update an sql database table
     223              :  *
     224              :  *  The DEFAULT clause is needed to convert the existing records to the new format
     225              :  *  and in case a new database is modified by an old program version.
     226              :  *
     227              :  *  This command extends diagrams by a stereotype field.
     228              :  */
     229              : static const char *DATA_DATABASE_ALTER_DIAGRAM_TABLE_STEREOTYPE =
     230              :     "ALTER TABLE diagrams "
     231              :     "ADD COLUMN stereotype TEXT DEFAULT \'\';";
     232              : 
     233              : /*!
     234              :  *  \brief string constant to create an sql database table
     235              :  */
     236              : static const char *DATA_DATABASE_CREATE_DIAGRAMELEMENT_TABLE =
     237              :     "CREATE TABLE IF NOT EXISTS diagramelements ( "
     238              :         "id INTEGER NOT NULL PRIMARY KEY ASC, "
     239              :         "diagram_id INTEGER NOT NULL, "
     240              :         "classifier_id INTEGER NOT NULL, "
     241              :         "display_flags INTEGER NOT NULL, "
     242              :         "focused_feature_id INTEGER DEFAULT NULL, "  /* DEFAULT needed in case a new DB is modified by an old program version */
     243              :         "uuid TEXT NOT NULL DEFAULT \'\', "  /* DEFAULT needed in case a new DB is modified by an old program version */
     244              :         "FOREIGN KEY(diagram_id) REFERENCES diagrams(id), "
     245              :         "FOREIGN KEY(classifier_id) REFERENCES classifiers(id), "
     246              :         "FOREIGN KEY(focused_feature_id) REFERENCES features(id) "
     247              :     ");";
     248              : 
     249              : /*!
     250              :  *  \brief string constant to update an sql database table
     251              :  *
     252              :  *  The DEFAULT clause is needed to convert the existing records to the new format.
     253              :  *
     254              :  *  This command extends diagramelements by a uuid field.
     255              :  *  \see http://sqlite.org/lang_altertable.html
     256              :  */
     257              : static const char *DATA_DATABASE_ALTER_DIAGRAMELEMENT_TABLE_UUID =
     258              :     "ALTER TABLE diagramelements "
     259              :     "ADD COLUMN uuid TEXT NOT NULL DEFAULT \'\';";
     260              : 
     261              : /*!
     262              :  *  \brief string constant to update an sql database table
     263              :  *
     264              :  *  A uuid is generated and updated wherever missing.
     265              :  *  The subselect-WHERE clause is needed to generate for each row a different uuid.
     266              :  */
     267              : static const char *DATA_DATABASE_UPDATE_DIAGRAMELEMENT_UUID =
     268              :     "UPDATE diagramelements SET uuid=(SELECT " DATA_DATABASE_CREATE_UUID " WHERE diagramelements.id!=-1) WHERE uuid=\'\';";
     269              : 
     270              : /*!
     271              :  *  \brief string constant to start a transaction
     272              :  *
     273              :  *  \see http://sqlite.org/lang.html
     274              :  */
     275              : static const char *DATA_DATABASE_BEGIN_TRANSACTION =
     276              :     "BEGIN TRANSACTION;";
     277              : 
     278              : /*!
     279              :  *  \brief string constant to commit a transaction
     280              :  *
     281              :  *  \see http://sqlite.org/lang.html
     282              :  */
     283              : static const char *DATA_DATABASE_COMMIT_TRANSACTION =
     284              :     "COMMIT TRANSACTION;";
     285              : 
     286              : /*!
     287              :  *  \brief keep track of the next unused revision identifier
     288              :  *
     289              :  *  because data_database_set_revision() may set an already used identifier
     290              :  */
     291              : static uint32_t data_database_unused_revision = 0;
     292              : 
     293              : /* ================================ Lifecycle ================================ */
     294              : 
     295           62 : void data_database_init ( data_database_t *this_ )
     296              : {
     297           62 :     U8_TRACE_BEGIN();
     298           62 :     U8_LOG_EVENT_INT( "compiled against sqlite3:    ", SQLITE_VERSION_NUMBER );
     299           62 :     U8_LOG_EVENT_STR( "linked to sqlite3_libversion:", sqlite3_libversion() );
     300              : 
     301           62 :     (*this_).db_file_name = utf8stringbuf_new( (*this_).private_db_file_name_buffer, sizeof((*this_).private_db_file_name_buffer) );
     302           62 :     utf8stringbuf_clear( &((*this_).db_file_name) );
     303              : 
     304           62 :     g_mutex_init ( &((*this_).lock_on_write) );
     305           62 :     g_mutex_lock ( &((*this_).lock_on_write) );  /* (*this_).locked_on_write may only be changed when having the lock */
     306           62 :     (*this_).locked_on_write = false;
     307           62 :     g_mutex_unlock ( &((*this_).lock_on_write) );  /* this call manages a memory barrier that allows for cache coherence */
     308              : 
     309           62 :     u8_error_t result = data_database_lock_on_write( this_ );
     310              :     {
     311           62 :         (*this_).db_state = DATA_DATABASE_STATE_CLOSED;
     312           62 :         (*this_).transaction_recursion = 0;
     313           62 :         (*this_).revision = ( data_database_unused_revision++ );
     314              :     }
     315           62 :     result |= data_database_unlock_on_write( this_ );
     316           62 :     if( result != U8_ERROR_NONE )
     317              :     {
     318            0 :         assert(false);
     319              :     }
     320              : 
     321           62 :     data_change_notifier_init ( &((*this_).notifier) );
     322           62 :     data_database_private_clear_db_listener_list( this_ );
     323              : 
     324           62 :     U8_TRACE_END();
     325           62 : }
     326              : 
     327           62 : u8_error_t data_database_private_open ( data_database_t *this_, const char* db_file_path, int sqlite3_flags )
     328              : {
     329           62 :     U8_TRACE_BEGIN();
     330           62 :     assert( NULL != db_file_path );
     331              :     /* there should not be pending transactions when calling open */
     332           62 :     assert( (*this_).transaction_recursion == 0 );
     333              :     int sqlite_err;
     334           62 :     u8_error_t result = U8_ERROR_NONE;
     335           62 :     bool notify_listeners = false;
     336              : 
     337           62 :     result |= data_database_lock_on_write( this_ );
     338              : 
     339           62 :     if ( (*this_).db_state != DATA_DATABASE_STATE_CLOSED )
     340              :     {
     341            0 :         U8_TRACE_INFO("data_database_open called on database that was not closed.");
     342            0 :         result |= U8_ERROR_INVALID_REQUEST;
     343              :     }
     344              :     else
     345              :     {
     346           62 :         utf8stringbuf_copy_str( &((*this_).db_file_name), db_file_path );
     347              : 
     348           62 :         U8_LOG_EVENT_STR( "sqlite3_open_v2:", utf8stringbuf_get_string( &((*this_).db_file_name) ) );
     349           62 :         sqlite_err = sqlite3_open_v2( utf8stringbuf_get_string( &((*this_).db_file_name) ),
     350              :                                       &((*this_).db),
     351              :                                       sqlite3_flags,
     352              :                                       NULL
     353              :                                     );
     354           62 :         if ( SQLITE_OK != sqlite_err )
     355              :         {
     356            0 :             U8_LOG_ERROR_INT( "sqlite3_open_v2() failed:", sqlite_err );
     357            0 :             U8_LOG_ERROR_STR( "sqlite3_open_v2() failed:", utf8stringbuf_get_string( &((*this_).db_file_name) ) );
     358            0 :             (*this_).db_state = DATA_DATABASE_STATE_CLOSED;
     359            0 :             result |= U8_ERROR_NO_DB;  /* no db to use */
     360              :         }
     361              :         else
     362              :         {
     363              :             u8_error_t init_err;
     364           62 :             init_err = data_database_private_initialize_tables( this_ );
     365           62 :             if ( init_err == U8_ERROR_NONE )
     366              :             {
     367           62 :                 init_err = data_database_private_upgrade_tables( this_ );
     368              :             }
     369              : 
     370           62 :             if ( init_err == U8_ERROR_NONE )
     371              :             {
     372              :                 (*this_).db_state
     373          124 :                     = ( (sqlite3_flags & SQLITE_OPEN_MEMORY) == 0 )
     374              :                     ? DATA_DATABASE_STATE_OPEN
     375           62 :                     : DATA_DATABASE_STATE_IN_MEM;
     376           62 :                 notify_listeners = true;
     377              :             }
     378              :             else
     379              :             {
     380            0 :                 U8_LOG_EVENT_STR( "sqlite3_close:", utf8stringbuf_get_string( &((*this_).db_file_name) ) );
     381            0 :                 sqlite_err = sqlite3_close( (*this_).db );
     382            0 :                 if ( SQLITE_OK != sqlite_err )
     383              :                 {
     384            0 :                     U8_LOG_ERROR_INT( "sqlite3_close() failed:", sqlite_err );
     385              :                 }
     386            0 :                 utf8stringbuf_clear( &((*this_).db_file_name) );
     387            0 :                 (*this_).db_state = DATA_DATABASE_STATE_CLOSED;
     388              :             }
     389           62 :             result |= init_err;
     390              :         }
     391              :     }
     392              : 
     393           62 :     result |= data_database_unlock_on_write( this_ );
     394              : 
     395           62 :     if ( notify_listeners )
     396              :     {
     397              :         /* do sent notifications is active by default on a newly opened db */
     398           62 :         data_change_notifier_disable_stealth_mode( &((*this_).notifier) );
     399              : 
     400              :         /* inform readers and writers on open */
     401           62 :         result |= data_database_private_notify_db_listeners( this_, DATA_DATABASE_LISTENER_SIGNAL_DB_OPENED );
     402              : 
     403              :         /* inform listeners on changes */
     404           62 :         data_change_notifier_emit_signal( &((*this_).notifier),
     405              :                                           DATA_CHANGE_EVENT_TYPE_DB_OPENED,
     406              :                                           DATA_TABLE_VOID,
     407              :                                           DATA_ROW_VOID,
     408              :                                           DATA_TABLE_VOID,
     409              :                                           DATA_ROW_VOID
     410              :                                         );
     411              :     }
     412              : 
     413           62 :     U8_TRACE_END_ERR( result );
     414           62 :     return result;
     415              : }
     416              : 
     417           62 : u8_error_t data_database_close ( data_database_t *this_ )
     418              : {
     419           62 :     U8_TRACE_BEGIN();
     420              :     /* there should not be pending transactions when calling cloas */
     421           62 :     assert( (*this_).transaction_recursion == 0 );
     422              :     int sqlite_err;
     423           62 :     u8_error_t result = U8_ERROR_NONE;
     424           62 :     bool notify_change_listeners = false;
     425              : 
     426              :     /* do sent notifications before closing: */
     427           62 :     if ( data_database_is_open( this_ ) )
     428              :     {
     429              :         /* do sent notifications when closing: */
     430           62 :         data_change_notifier_disable_stealth_mode( &((*this_).notifier) );
     431              : 
     432              :         /* prepare close */
     433           62 :         data_change_notifier_emit_signal( &((*this_).notifier),
     434              :                                           DATA_CHANGE_EVENT_TYPE_DB_PREPARE_CLOSE,
     435              :                                           DATA_TABLE_VOID,
     436              :                                           DATA_ROW_VOID,
     437              :                                           DATA_TABLE_VOID,
     438              :                                           DATA_ROW_VOID
     439              :                                         );
     440              : 
     441              :         /* inform readers and writers on close */
     442           62 :         result |= data_database_private_notify_db_listeners( this_, DATA_DATABASE_LISTENER_SIGNAL_PREPARE_CLOSE );
     443              :     }
     444              : 
     445           62 :     result |= data_database_lock_on_write( this_ );
     446              : 
     447           62 :     if ( (*this_).db_state != DATA_DATABASE_STATE_CLOSED )
     448              :     {
     449              :         /* perform close */
     450           62 :         U8_LOG_EVENT_STR( "sqlite3_close:", utf8stringbuf_get_string( &((*this_).db_file_name) ) );
     451           62 :         sqlite_err = sqlite3_close( (*this_).db );
     452           62 :         if ( SQLITE_OK != sqlite_err )
     453              :         {
     454            0 :             U8_LOG_ERROR_INT( "sqlite3_close() failed:", sqlite_err );
     455            0 :             result |= U8_ERROR_AT_DB;
     456              :         }
     457              : 
     458           62 :         utf8stringbuf_clear( &((*this_).db_file_name) );
     459           62 :         (*this_).db_state = DATA_DATABASE_STATE_CLOSED;
     460           62 :         (*this_).transaction_recursion = 0;
     461              : 
     462           62 :         notify_change_listeners = true;
     463              :     }
     464              :     else
     465              :     {
     466            0 :         U8_TRACE_INFO("data_database_close called on database that was not open.");
     467            0 :         result |= U8_ERROR_INVALID_REQUEST;
     468              :     }
     469              : 
     470           62 :     result |= data_database_unlock_on_write( this_ );
     471              : 
     472           62 :     if ( notify_change_listeners )
     473              :     {
     474              :         /* inform listeners on changes */
     475           62 :         data_change_notifier_emit_signal( &((*this_).notifier),
     476              :                                           DATA_CHANGE_EVENT_TYPE_DB_CLOSED,
     477              :                                           DATA_TABLE_VOID,
     478              :                                           DATA_ROW_VOID,
     479              :                                           DATA_TABLE_VOID,
     480              :                                           DATA_ROW_VOID
     481              :         );
     482              :     }
     483              : 
     484           62 :     U8_TRACE_END_ERR( result );
     485           62 :     return result;
     486              : }
     487              : 
     488           62 : void data_database_destroy ( data_database_t *this_ )
     489              : {
     490           62 :     U8_TRACE_BEGIN();
     491              :     /* there should not be pending transactions when calling destroy */
     492           62 :     assert( (*this_).transaction_recursion == 0 );
     493              : 
     494           62 :     data_database_private_clear_db_listener_list( this_ );
     495           62 :     if ( data_database_is_open( this_ ) )
     496              :     {
     497            1 :         data_database_close( this_ );
     498              :     }
     499           62 :     data_change_notifier_destroy( &((*this_).notifier) );
     500              : 
     501           62 :     u8_error_t result = data_database_lock_on_write( this_ );
     502              :     {
     503           62 :         (*this_).transaction_recursion = 0;
     504              :     }
     505           62 :     result |= data_database_unlock_on_write( this_ );
     506           62 :     if( result != U8_ERROR_NONE )
     507              :     {
     508            0 :         assert(false);
     509              :     }
     510              : 
     511              :     /* g_mutex_clear ( &((*this_).lock_on_write) ); -- must not be called because this GMutex is not on the stack */
     512              : 
     513           62 :     U8_TRACE_END();
     514           62 : }
     515              : 
     516           62 : u8_error_t data_database_private_initialize_tables( data_database_t *this_ )
     517              : {
     518           62 :     U8_TRACE_BEGIN();
     519           62 :     u8_error_t result = U8_ERROR_NONE;
     520              : 
     521              :     /* since the database is locked during data_database_private_initialize_tables */
     522              :     /* we cannot call data_database_transaction_begin or data_database_in_transaction_execute here */
     523              : 
     524           62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_HEAD_TABLE, false );
     525           62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_CLASSIFIER_TABLE, false );
     526           62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_RELATIONSHIP_TABLE, false );
     527           62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_FEATURE_TABLE, false );
     528           62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_DIAGRAM_TABLE, false );
     529           62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_DIAGRAMELEMENT_TABLE, false );
     530              : 
     531           62 :     U8_TRACE_END_ERR( result );
     532           62 :     return result;
     533              : }
     534              : 
     535           62 : u8_error_t data_database_private_upgrade_tables( data_database_t *this_ )
     536              : {
     537           62 :     U8_TRACE_BEGIN();
     538           62 :     u8_error_t result = U8_ERROR_NONE;
     539              : 
     540              :     /* update table classifiers from version 1.32.1 or earlier to later versions with diagram.display_flags */
     541           62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_DIAGRAM_TABLE_1, true );
     542              : 
     543              :     /* update all 5 tables from version 1.32.1 (no uuid) to later versions with uuid field */
     544           62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_CLASSIFIER_TABLE_UUID, true );
     545           62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_CLASSIFIER_UUID, true );
     546           62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_RELATIONSHIP_TABLE_UUID, true );
     547           62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_RELATIONSHIP_UUID, true );
     548           62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_FEATURE_TABLE_UUID, true );
     549           62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_FEATURE_UUID, true );
     550           62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_DIAGRAM_TABLE_UUID, true );
     551           62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_DIAGRAM_UUID, true );
     552           62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_DIAGRAMELEMENT_TABLE_UUID, true );
     553           62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_DIAGRAMELEMENT_UUID, true );
     554              : 
     555              :     /* update table diagrams and relationships from version 1.46.0 or earlier to later versions with stereotype */
     556              :     /* do not care for "already existed" errors: */
     557           62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_RELATIONSHIP_TABLE_STEREOTYPE, true );
     558           62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_DIAGRAM_TABLE_STEREOTYPE, true );
     559              : 
     560           62 :     if ( u8_error_contains( result, U8_ERROR_READ_ONLY_DB ) )
     561              :     {
     562            0 :         U8_LOG_EVENT( "sqlite3 database is read only." );
     563            0 :         result = u8_error_more_than( result, U8_ERROR_READ_ONLY_DB ) ? U8_ERROR_AT_DB : U8_ERROR_NONE;
     564              :     }
     565              : 
     566           62 :     U8_TRACE_END_ERR( result );
     567           62 :     return result;
     568              : }
     569              : 
     570              : /* ================================ Actions on DB ================================ */
     571              : 
     572            0 : u8_error_t data_database_flush_caches ( data_database_t *this_ )
     573              : {
     574            0 :     U8_TRACE_BEGIN();
     575            0 :     u8_error_t result = U8_ERROR_NONE;
     576              : 
     577            0 :     if ( data_database_is_open( this_ ) )
     578              :     {
     579            0 :         U8_LOG_EVENT_INT( "sqlite3_libversion_number()", sqlite3_libversion_number() );
     580            0 :         if ( sqlite3_libversion_number() >= 3010000 )
     581              :         {
     582              :             /* available if sqlite newer than 2016-01-06 (3.10.0) */
     583              : #if ( SQLITE_VERSION_NUMBER >= 3010000 )
     584              :             int sqlite_err;
     585            0 :             U8_LOG_EVENT( "sqlite3_db_cacheflush" );
     586            0 :             sqlite_err = sqlite3_db_cacheflush( (*this_).db );
     587            0 :             if ( SQLITE_OK != sqlite_err )
     588              :             {
     589            0 :                 U8_LOG_ERROR_INT( "sqlite3_db_cacheflush() failed:", sqlite_err );
     590            0 :                 result = U8_ERROR_AT_DB;
     591              :             }
     592              : #else
     593              :             U8_LOG_WARNING_INT( "The compile-time version of sqlite3 did not provide the sqlite3_db_cacheflush() function.",
     594              :                                 SQLITE_VERSION_NUMBER
     595              :                               );
     596              : #endif
     597              :         }
     598              :         else
     599              :         {
     600            0 :             U8_LOG_WARNING_INT( "The runtime-time version of sqlite3 does not provide the sqlite3_db_cacheflush() function.",
     601              :                                 sqlite3_libversion_number()
     602              :                               );
     603              :         }
     604              :     }
     605              :     else
     606              :     {
     607            0 :         result = U8_ERROR_NO_DB;
     608              :     }
     609              : 
     610            0 :     U8_TRACE_END_ERR( result );
     611            0 :     return result;
     612              : }
     613              : 
     614            0 : u8_error_t data_database_trace_stats ( data_database_t *this_ )
     615              : {
     616            0 :     U8_TRACE_BEGIN();
     617            0 :     u8_error_t result = U8_ERROR_NONE;
     618              : 
     619            0 :     if ( data_database_is_open( this_ ) )
     620              :     {
     621              :         sqlite3_int64 use;
     622              :         sqlite3_int64 max;
     623              : 
     624            0 :         use = sqlite3_memory_used();
     625            0 :         max = sqlite3_memory_highwater(false);
     626            0 :         U8_TRACE_INFO_INT_INT( "sqlite3_memory_used/highwater():", use, max );
     627              :     }
     628              :     else
     629              :     {
     630            0 :         U8_TRACE_INFO( "database not open." );
     631              :     }
     632              : 
     633            0 :     U8_TRACE_END_ERR( result );
     634            0 :     return result;
     635              : }
     636              : 
     637         1799 : u8_error_t data_database_transaction_begin ( data_database_t *this_ )
     638              : {
     639         1799 :     U8_TRACE_BEGIN();
     640              :     /* nesting of transactions should not be greater than 2. You may increase this limit if needed. */
     641         1799 :     assert( (*this_).transaction_recursion < 2 );
     642         1799 :     u8_error_t result = U8_ERROR_NONE;
     643              :     int sqlite_err;
     644         1799 :     char *error_msg = NULL;
     645         1799 :     sqlite3 *db = data_database_get_database_ptr( this_ );
     646              : 
     647         1799 :     if ( data_database_is_open( this_ ) )
     648              :     {
     649         1799 :         if ( (*this_).transaction_recursion == 0 )
     650              :         {
     651         1791 :             U8_LOG_EVENT_STR( "sqlite3_exec:", DATA_DATABASE_BEGIN_TRANSACTION );
     652         1791 :             sqlite_err = sqlite3_exec( db, DATA_DATABASE_BEGIN_TRANSACTION, NULL, NULL, &error_msg );
     653         1791 :             if ( SQLITE_OK != sqlite_err )
     654              :             {
     655            0 :                 U8_LOG_ERROR_STR( "sqlite3_exec() failed:", DATA_DATABASE_BEGIN_TRANSACTION );
     656            0 :                 U8_LOG_ERROR_INT( "sqlite3_exec() failed:", sqlite_err );
     657            0 :                 result |= U8_ERROR_AT_DB;
     658              :             }
     659         1791 :             if ( error_msg != NULL )
     660              :             {
     661            0 :                 U8_LOG_ERROR_STR( "sqlite3_exec() failed:", error_msg );
     662            0 :                 sqlite3_free( error_msg );
     663            0 :                 error_msg = NULL;
     664              :             }
     665              :         }
     666         1799 :         (*this_).transaction_recursion ++;
     667              :     }
     668              :     else
     669              :     {
     670            0 :         U8_LOG_WARNING_STR( "database not open. cannot execute", DATA_DATABASE_BEGIN_TRANSACTION );
     671            0 :         result = U8_ERROR_NO_DB;
     672              :     }
     673              : 
     674         1799 :     U8_TRACE_END_ERR( result );
     675         1799 :     return result;
     676              : }
     677              : 
     678         1799 : u8_error_t data_database_transaction_commit ( data_database_t *this_ )
     679              : {
     680         1799 :     U8_TRACE_BEGIN();
     681              :     /* there should be at least 1 pending transaction */
     682         1799 :     assert( (*this_).transaction_recursion > 0 );
     683         1799 :     u8_error_t result = U8_ERROR_NONE;
     684              :     int sqlite_err;
     685         1799 :     char *error_msg = NULL;
     686         1799 :     sqlite3 *db = data_database_get_database_ptr( this_ );
     687              : 
     688         1799 :     if ( data_database_is_open( this_ ) )
     689              :     {
     690         1799 :         if ( (*this_).transaction_recursion == 1 )
     691              :         {
     692         1791 :             U8_LOG_EVENT_STR( "sqlite3_exec:", DATA_DATABASE_COMMIT_TRANSACTION );
     693         1791 :             sqlite_err = sqlite3_exec( db, DATA_DATABASE_COMMIT_TRANSACTION, NULL, NULL, &error_msg );
     694         1791 :             if ( SQLITE_OK != sqlite_err )
     695              :             {
     696            0 :                 U8_LOG_ERROR_STR( "sqlite3_exec() failed:", DATA_DATABASE_COMMIT_TRANSACTION );
     697            0 :                 U8_LOG_ERROR_INT( "sqlite3_exec() failed:", sqlite_err );
     698            0 :                 result |= U8_ERROR_AT_DB;
     699              :             }
     700         1791 :             if ( error_msg != NULL )
     701              :             {
     702            0 :                 U8_LOG_ERROR_STR( "sqlite3_exec() failed:", error_msg );
     703            0 :                 sqlite3_free( error_msg );
     704            0 :                 error_msg = NULL;
     705              :             }
     706              : 
     707              :             /* increase the revision id */
     708              :             u8_error_t locking_error;
     709         1791 :             locking_error = data_database_lock_on_write( this_ );
     710         1791 :             (*this_).revision = ( data_database_unused_revision++ );
     711         1791 :             locking_error |= data_database_unlock_on_write( this_ );
     712         1791 :             assert( locking_error == U8_ERROR_NONE );
     713              :             (void) locking_error;  /* this should not happen in RELEASE mode */
     714              :         }
     715         1799 :         (*this_).transaction_recursion --;
     716              :     }
     717              :     else
     718              :     {
     719            0 :         U8_LOG_WARNING_STR( "database not open. cannot execute", DATA_DATABASE_COMMIT_TRANSACTION );
     720            0 :         result = U8_ERROR_NO_DB;
     721              :     }
     722              : 
     723         1799 :     U8_TRACE_END_ERR( result );
     724         1799 :     return result;
     725              : }
     726              : 
     727         1476 : u8_error_t data_database_in_transaction_create ( data_database_t *this_, const char* sql_statement, data_row_t* out_new_id )
     728              : {
     729         1476 :     U8_TRACE_BEGIN();
     730         1476 :     assert( NULL != sql_statement );
     731         1476 :     u8_error_t result = U8_ERROR_NONE;
     732              :     int sqlite_err;
     733         1476 :     char *error_msg = NULL;
     734         1476 :     sqlite3 *const db = (*this_).db;
     735              : 
     736         1476 :     if ( data_database_is_open( this_ ) )
     737              :     {
     738         1476 :         U8_LOG_EVENT( "sqlite3_exec: sql_statement (see trace)" );
     739         1476 :         U8_TRACE_INFO_STR( "sqlite3_exec:", sql_statement );
     740         1476 :         sqlite_err = sqlite3_exec( db, sql_statement, NULL, NULL, &error_msg );
     741         1476 :         if ( SQLITE_CONSTRAINT == (0xff & sqlite_err) )
     742              :         {
     743              :             /* This case happens if id is not unique and/or if a classifier name is not unique*/
     744           12 :             U8_LOG_ERROR( "sqlite3_exec() failed due to UNIQUE constraint: sql_statement (see trace)" );
     745           12 :             U8_TRACE_INFO_STR( "sqlite3_exec() failed due to UNIQUE constraint:", sql_statement );
     746           12 :             result |= U8_ERROR_DUPLICATE;
     747              :         }
     748         1464 :         else if ( SQLITE_OK != sqlite_err )
     749              :         {
     750            0 :             U8_LOG_ERROR( "sqlite3_exec() failed: sql_statement (see trace)" );
     751            0 :             U8_LOG_ERROR_INT( "sqlite3_exec() failed:", sqlite_err );
     752            0 :             U8_TRACE_INFO_STR( "sqlite3_exec:", sql_statement );
     753            0 :             result |= (sqlite_err == SQLITE_READONLY) ? U8_ERROR_READ_ONLY_DB : U8_ERROR_AT_DB;
     754              :         }
     755         1476 :         if ( error_msg != NULL )
     756              :         {
     757           12 :             U8_LOG_ERROR_STR( "sqlite3_exec() failed:", error_msg );
     758           12 :             sqlite3_free( error_msg );
     759           12 :             error_msg = NULL;
     760              :         }
     761              : 
     762         1476 :         if ( NULL != out_new_id )
     763              :         {
     764         1476 :             if ( SQLITE_OK == sqlite_err )
     765              :             {
     766              :                 data_row_t new_id;
     767         1464 :                 new_id = sqlite3_last_insert_rowid(db);
     768         1464 :                 U8_LOG_EVENT_INT( "sqlite3_last_insert_rowid():", new_id );
     769         1464 :                 *out_new_id = new_id;
     770              :             }
     771              :         }
     772              :     }
     773              :     else
     774              :     {
     775            0 :         U8_LOG_WARNING( "database not open. cannot execute sql_statement (see trace)" );
     776            0 :         U8_TRACE_INFO_STR( "database not open. cannot execute", sql_statement );
     777            0 :         result = U8_ERROR_NO_DB;
     778              :     }
     779              : 
     780         1476 :     U8_TRACE_END_ERR( result );
     781         1476 :     return result;
     782              : }
     783              : 
     784          316 : u8_error_t data_database_in_transaction_execute ( data_database_t *this_, const char* sql_statement )
     785              : {
     786          316 :     U8_TRACE_BEGIN();
     787          316 :     assert( NULL != sql_statement );
     788          316 :     u8_error_t result = U8_ERROR_NONE;
     789              :     int sqlite_err;
     790          316 :     char *error_msg = NULL;
     791          316 :     sqlite3 *db = (*this_).db;
     792              : 
     793          316 :     if ( data_database_is_open( this_ ) )
     794              :     {
     795          316 :         U8_LOG_EVENT( "sqlite3_exec: sql_statement (see trace)" );
     796          316 :         U8_TRACE_INFO_STR( "sqlite3_exec:", sql_statement );
     797          316 :         sqlite_err = sqlite3_exec( db, sql_statement, NULL, NULL, &error_msg );
     798          316 :         if ( SQLITE_CONSTRAINT == (0xff & sqlite_err) )
     799              :         {
     800            0 :             U8_LOG_ERROR( "sqlite3_exec() failed due to UNIQUE constraint: sql_statement (see trace)" );
     801            0 :             U8_TRACE_INFO_STR( "sqlite3_exec() failed due to UNIQUE constraint:", sql_statement );
     802            0 :             result |= U8_ERROR_DUPLICATE_NAME;
     803              :         }
     804          316 :         else if ( SQLITE_OK != sqlite_err )
     805              :         {
     806            0 :             U8_LOG_ERROR( "sqlite3_exec() failed: sql_statement (see trace)" );
     807            0 :             U8_LOG_ERROR_INT( "sqlite3_exec() failed:", sqlite_err );
     808            0 :             U8_TRACE_INFO_STR( "sqlite3_exec() failed:", sql_statement );
     809            0 :             result |= (sqlite_err == SQLITE_READONLY) ? U8_ERROR_READ_ONLY_DB : U8_ERROR_AT_DB;
     810              :         }
     811          316 :         if ( error_msg != NULL )
     812              :         {
     813            0 :             U8_LOG_ERROR_STR( "sqlite3_exec() failed:", error_msg );
     814            0 :             sqlite3_free( error_msg );
     815            0 :             error_msg = NULL;
     816              :         }
     817              :     }
     818              :     else
     819              :     {
     820            0 :         U8_LOG_WARNING( "database not open. cannot execute sql_statement (see trace)" );
     821            0 :         U8_TRACE_INFO_STR( "database not open. cannot execute", sql_statement );
     822            0 :         result = U8_ERROR_NO_DB;
     823              :     }
     824              : 
     825          316 :     U8_TRACE_END_ERR( result );
     826          316 :     return result;
     827              : }
     828              : 
     829              : /* ================================ Information ================================ */
     830              : 
     831              : /* ================================ Change Listener ================================ */
     832              : 
     833          235 : u8_error_t data_database_add_db_listener( data_database_t *this_, data_database_listener_t *listener )
     834              : {
     835          235 :     U8_TRACE_BEGIN();
     836          235 :     assert( NULL != listener );
     837          235 :     u8_error_t result = U8_ERROR_NONE;
     838          235 :     bool already_registered = false;
     839              : 
     840          235 :     result |= data_database_lock_on_write( this_ );
     841              : 
     842          235 :     int pos = -1;
     843         4935 :     for( int index = 0; index < DATA_DATABASE_MAX_LISTENERS; index ++ )
     844              :     {
     845         4700 :         if ( NULL == (*this_).listener_list[index] )
     846              :         {
     847         4162 :             pos = index;
     848              :         }
     849         4700 :         if ( listener == (*this_).listener_list[index] )
     850              :         {
     851            1 :             already_registered = true;
     852              :         }
     853              :     }
     854              : 
     855          235 :     if ( already_registered )
     856              :     {
     857            1 :         U8_LOG_ERROR( "Listener already registered." );
     858            1 :         result |= U8_ERROR_INVALID_REQUEST;
     859              :     }
     860          234 :     else if ( -1 != pos )
     861              :     {
     862          233 :         (*this_).listener_list[pos] = listener;
     863              :     }
     864              :     else
     865              :     {
     866            1 :         U8_LOG_ERROR_INT( "Maximum number of listeners reached.", DATA_DATABASE_MAX_LISTENERS );
     867            1 :         result |= U8_ERROR_ARRAY_BUFFER_EXCEEDED;
     868              :     }
     869              : 
     870          235 :     result |= data_database_unlock_on_write( this_ );
     871              : 
     872          235 :     U8_TRACE_END_ERR( result );
     873          235 :     return result;
     874              : }
     875              : 
     876          214 : u8_error_t data_database_remove_db_listener( data_database_t *this_, data_database_listener_t *listener )
     877              : {
     878          214 :     U8_TRACE_BEGIN();
     879              : 
     880          214 :     assert( NULL != listener );
     881              : 
     882          214 :     u8_error_t result = U8_ERROR_NONE;
     883          214 :     int count_closed = 0;
     884              : 
     885          214 :     result |= data_database_lock_on_write( this_ );
     886              : 
     887         4494 :     for( int index = 0; index < DATA_DATABASE_MAX_LISTENERS; index ++ )
     888              :     {
     889         4280 :         if ( (*this_).listener_list[index] == listener )
     890              :         {
     891          213 :             (*this_).listener_list[index] = NULL;
     892          213 :             count_closed ++;
     893              :         }
     894              :     }
     895              : 
     896          214 :     result |= data_database_unlock_on_write( this_ );
     897              : 
     898          214 :     if ( count_closed == 0 )
     899              :     {
     900            1 :         U8_LOG_ERROR( "listener not found" );
     901            1 :         result |= U8_ERROR_INVALID_REQUEST;
     902              :     }
     903              : 
     904          214 :     U8_TRACE_END_ERR( result );
     905          214 :     return result;
     906              : }
     907              : 
     908          128 : u8_error_t data_database_private_notify_db_listeners( data_database_t *this_, data_database_listener_signal_t signal_id )
     909              : {
     910          128 :     U8_TRACE_BEGIN();
     911              :     data_database_listener_t *(listener_list_copy[DATA_DATABASE_MAX_LISTENERS]);
     912          128 :     u8_error_t result = U8_ERROR_NONE;
     913              : 
     914          128 :     result |= data_database_lock_on_write( this_ );
     915              : 
     916          128 :     memcpy( listener_list_copy, (*this_).listener_list, sizeof(listener_list_copy) );
     917              : 
     918          128 :     result |= data_database_unlock_on_write( this_ );
     919              : 
     920         2688 :     for( int index = 0; index < DATA_DATABASE_MAX_LISTENERS; index ++ )
     921              :     {
     922         2560 :         if ( NULL != listener_list_copy[index] )
     923              :         {
     924           49 :             data_database_listener_notify( listener_list_copy[index], signal_id );
     925              :         }
     926              :     }
     927              : 
     928          128 :     U8_TRACE_END_ERR( result );
     929          128 :     return result;
     930              : }
     931              : 
     932              : /* ================================ Lifecycle Lock ================================ */
     933              : 
     934              : 
     935              : /*
     936              : Copyright 2016-2025 Andreas Warnke
     937              : 
     938              : Licensed under the Apache License, Version 2.0 (the "License");
     939              : you may not use this file except in compliance with the License.
     940              : You may obtain a copy of the License at
     941              : 
     942              :     http://www.apache.org/licenses/LICENSE-2.0
     943              : 
     944              : Unless required by applicable law or agreed to in writing, software
     945              : distributed under the License is distributed on an "AS IS" BASIS,
     946              : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     947              : See the License for the specific language governing permissions and
     948              : limitations under the License.
     949              : */
        

Generated by: LCOV version 2.0-1