LCOV - code coverage report
Current view: top level - data/source/storage - data_database.c (source / functions) Hit Total Coverage
Test: crystal-facet-uml_v1.65.6_covts Lines: 237 318 74.5 %
Date: 2025-09-25 21:07:53 Functions: 13 15 86.7 %

          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          64 : void data_database_init ( data_database_t *this_ )
     296             : {
     297          64 :     U8_TRACE_BEGIN();
     298          64 :     U8_LOG_EVENT_INT( "compiled against sqlite3:    ", SQLITE_VERSION_NUMBER );
     299          64 :     U8_LOG_EVENT_STR( "linked to sqlite3_libversion:", sqlite3_libversion() );
     300             : 
     301          64 :     (*this_).db_file_name = utf8stringbuf_new( (*this_).private_db_file_name_buffer, sizeof((*this_).private_db_file_name_buffer) );
     302          64 :     utf8stringbuf_clear( &((*this_).db_file_name) );
     303             : 
     304          64 :     g_mutex_init ( &((*this_).lock_on_write) );
     305          64 :     g_mutex_lock ( &((*this_).lock_on_write) );  /* (*this_).locked_on_write may only be changed when having the lock */
     306          64 :     (*this_).locked_on_write = false;
     307          64 :     g_mutex_unlock ( &((*this_).lock_on_write) );  /* this call manages a memory barrier that allows for cache coherence */
     308             : 
     309          64 :     u8_error_t result = data_database_lock_on_write( this_ );
     310             :     {
     311          64 :         (*this_).db_state = DATA_DATABASE_STATE_CLOSED;
     312          64 :         (*this_).transaction_recursion = 0;
     313          64 :         (*this_).revision = ( data_database_unused_revision++ );
     314             :     }
     315          64 :     result |= data_database_unlock_on_write( this_ );
     316          64 :     if( result != U8_ERROR_NONE )
     317             :     {
     318           0 :         assert(false);
     319             :     }
     320             : 
     321          64 :     data_change_notifier_init ( &((*this_).notifier) );
     322          64 :     data_database_private_clear_db_listener_list( this_ );
     323             : 
     324          64 :     U8_TRACE_END();
     325          64 : }
     326             : 
     327          64 : u8_error_t data_database_private_open ( data_database_t *this_, const char* db_file_path, int sqlite3_flags )
     328             : {
     329          64 :     U8_TRACE_BEGIN();
     330          64 :     assert( NULL != db_file_path );
     331             :     /* there should not be pending transactions when calling open */
     332          64 :     assert( (*this_).transaction_recursion == 0 );
     333             :     int sqlite_err;
     334          64 :     u8_error_t result = U8_ERROR_NONE;
     335          64 :     bool notify_listeners = false;
     336             : 
     337          64 :     result |= data_database_lock_on_write( this_ );
     338             : 
     339          64 :     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          64 :         utf8stringbuf_copy_str( &((*this_).db_file_name), db_file_path );
     347             : 
     348          64 :         U8_LOG_EVENT_STR( "sqlite3_open_v2:", utf8stringbuf_get_string( &((*this_).db_file_name) ) );
     349          64 :         sqlite_err = sqlite3_open_v2( utf8stringbuf_get_string( &((*this_).db_file_name) ),
     350             :                                       &((*this_).db),
     351             :                                       sqlite3_flags,
     352             :                                       NULL
     353             :                                     );
     354          64 :         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          64 :             init_err = data_database_private_initialize_tables( this_ );
     365          64 :             if ( init_err == U8_ERROR_NONE )
     366             :             {
     367          64 :                 init_err = data_database_private_upgrade_tables( this_ );
     368             :             }
     369             : 
     370          64 :             if ( init_err == U8_ERROR_NONE )
     371             :             {
     372             :                 (*this_).db_state
     373         128 :                     = ( (sqlite3_flags & SQLITE_OPEN_MEMORY) == 0 )
     374             :                     ? DATA_DATABASE_STATE_OPEN
     375          64 :                     : DATA_DATABASE_STATE_IN_MEM;
     376          64 :                 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          64 :             result |= init_err;
     390             :         }
     391             :     }
     392             : 
     393          64 :     result |= data_database_unlock_on_write( this_ );
     394             : 
     395          64 :     if ( notify_listeners )
     396             :     {
     397             :         /* do sent notifications is active by default on a newly opened db */
     398          64 :         data_change_notifier_disable_stealth_mode( &((*this_).notifier) );
     399             : 
     400             :         /* inform readers and writers on open */
     401          64 :         result |= data_database_private_notify_db_listeners( this_, DATA_DATABASE_LISTENER_SIGNAL_DB_OPENED );
     402             : 
     403             :         /* inform listeners on changes */
     404          64 :         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          64 :     U8_TRACE_END_ERR( result );
     414          64 :     return result;
     415             : }
     416             : 
     417          64 : u8_error_t data_database_close ( data_database_t *this_ )
     418             : {
     419          64 :     U8_TRACE_BEGIN();
     420             :     /* there should not be pending transactions when calling cloas */
     421          64 :     assert( (*this_).transaction_recursion == 0 );
     422             :     int sqlite_err;
     423          64 :     u8_error_t result = U8_ERROR_NONE;
     424          64 :     bool notify_change_listeners = false;
     425             : 
     426             :     /* do sent notifications before closing: */
     427          64 :     if ( data_database_is_open( this_ ) )
     428             :     {
     429             :         /* do sent notifications when closing: */
     430          64 :         data_change_notifier_disable_stealth_mode( &((*this_).notifier) );
     431             : 
     432             :         /* prepare close */
     433          64 :         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          64 :         result |= data_database_private_notify_db_listeners( this_, DATA_DATABASE_LISTENER_SIGNAL_PREPARE_CLOSE );
     443             :     }
     444             : 
     445          64 :     result |= data_database_lock_on_write( this_ );
     446             : 
     447          64 :     if ( (*this_).db_state != DATA_DATABASE_STATE_CLOSED )
     448             :     {
     449             :         /* perform close */
     450          64 :         U8_LOG_EVENT_STR( "sqlite3_close:", utf8stringbuf_get_string( &((*this_).db_file_name) ) );
     451          64 :         sqlite_err = sqlite3_close( (*this_).db );
     452          64 :         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          64 :         utf8stringbuf_clear( &((*this_).db_file_name) );
     459          64 :         (*this_).db_state = DATA_DATABASE_STATE_CLOSED;
     460          64 :         (*this_).transaction_recursion = 0;
     461             : 
     462          64 :         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          64 :     result |= data_database_unlock_on_write( this_ );
     471             : 
     472          64 :     if ( notify_change_listeners )
     473             :     {
     474             :         /* inform listeners on changes */
     475          64 :         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          64 :     U8_TRACE_END_ERR( result );
     485          64 :     return result;
     486             : }
     487             : 
     488          64 : void data_database_destroy ( data_database_t *this_ )
     489             : {
     490          64 :     U8_TRACE_BEGIN();
     491             :     /* there should not be pending transactions when calling destroy */
     492          64 :     assert( (*this_).transaction_recursion == 0 );
     493             : 
     494          64 :     data_database_private_clear_db_listener_list( this_ );
     495          64 :     if ( data_database_is_open( this_ ) )
     496             :     {
     497           1 :         data_database_close( this_ );
     498             :     }
     499          64 :     data_change_notifier_destroy( &((*this_).notifier) );
     500             : 
     501          64 :     u8_error_t result = data_database_lock_on_write( this_ );
     502             :     {
     503          64 :         (*this_).transaction_recursion = 0;
     504             :     }
     505          64 :     result |= data_database_unlock_on_write( this_ );
     506          64 :     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          64 :     U8_TRACE_END();
     514          64 : }
     515             : 
     516          64 : u8_error_t data_database_private_initialize_tables( data_database_t *this_ )
     517             : {
     518          64 :     U8_TRACE_BEGIN();
     519          64 :     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          64 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_HEAD_TABLE, false );
     525          64 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_CLASSIFIER_TABLE, false );
     526          64 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_RELATIONSHIP_TABLE, false );
     527          64 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_FEATURE_TABLE, false );
     528          64 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_DIAGRAM_TABLE, false );
     529          64 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_DIAGRAMELEMENT_TABLE, false );
     530             : 
     531          64 :     U8_TRACE_END_ERR( result );
     532          64 :     return result;
     533             : }
     534             : 
     535          64 : u8_error_t data_database_private_upgrade_tables( data_database_t *this_ )
     536             : {
     537          64 :     U8_TRACE_BEGIN();
     538          64 :     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          64 :     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          64 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_CLASSIFIER_TABLE_UUID, true );
     545          64 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_CLASSIFIER_UUID, true );
     546          64 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_RELATIONSHIP_TABLE_UUID, true );
     547          64 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_RELATIONSHIP_UUID, true );
     548          64 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_FEATURE_TABLE_UUID, true );
     549          64 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_FEATURE_UUID, true );
     550          64 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_DIAGRAM_TABLE_UUID, true );
     551          64 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_DIAGRAM_UUID, true );
     552          64 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_DIAGRAMELEMENT_TABLE_UUID, true );
     553          64 :     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          64 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_RELATIONSHIP_TABLE_STEREOTYPE, true );
     558          64 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_DIAGRAM_TABLE_STEREOTYPE, true );
     559             : 
     560          64 :     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          64 :     U8_TRACE_END_ERR( result );
     567          64 :     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        1811 : u8_error_t data_database_transaction_begin ( data_database_t *this_ )
     638             : {
     639        1811 :     U8_TRACE_BEGIN();
     640             :     /* nesting of transactions should not be greater than 2. You may increase this limit if needed. */
     641        1811 :     assert( (*this_).transaction_recursion < 2 );
     642        1811 :     u8_error_t result = U8_ERROR_NONE;
     643             :     int sqlite_err;
     644        1811 :     char *error_msg = NULL;
     645        1811 :     sqlite3 *db = data_database_get_database_ptr( this_ );
     646             : 
     647        1811 :     if ( data_database_is_open( this_ ) )
     648             :     {
     649        1811 :         if ( (*this_).transaction_recursion == 0 )
     650             :         {
     651        1803 :             U8_LOG_EVENT_STR( "sqlite3_exec:", DATA_DATABASE_BEGIN_TRANSACTION );
     652        1803 :             sqlite_err = sqlite3_exec( db, DATA_DATABASE_BEGIN_TRANSACTION, NULL, NULL, &error_msg );
     653        1803 :             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        1803 :             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        1811 :         (*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        1811 :     U8_TRACE_END_ERR( result );
     675        1811 :     return result;
     676             : }
     677             : 
     678        1811 : u8_error_t data_database_transaction_commit ( data_database_t *this_ )
     679             : {
     680        1811 :     U8_TRACE_BEGIN();
     681             :     /* there should be at least 1 pending transaction */
     682        1811 :     assert( (*this_).transaction_recursion > 0 );
     683        1811 :     u8_error_t result = U8_ERROR_NONE;
     684             :     int sqlite_err;
     685        1811 :     char *error_msg = NULL;
     686        1811 :     sqlite3 *db = data_database_get_database_ptr( this_ );
     687             : 
     688        1811 :     if ( data_database_is_open( this_ ) )
     689             :     {
     690        1811 :         if ( (*this_).transaction_recursion == 1 )
     691             :         {
     692        1803 :             U8_LOG_EVENT_STR( "sqlite3_exec:", DATA_DATABASE_COMMIT_TRANSACTION );
     693        1803 :             sqlite_err = sqlite3_exec( db, DATA_DATABASE_COMMIT_TRANSACTION, NULL, NULL, &error_msg );
     694        1803 :             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        1803 :             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        1803 :             locking_error = data_database_lock_on_write( this_ );
     710        1803 :             (*this_).revision = ( data_database_unused_revision++ );
     711        1803 :             locking_error |= data_database_unlock_on_write( this_ );
     712        1803 :             assert( locking_error == U8_ERROR_NONE );
     713             :             (void) locking_error;  /* this should not happen in RELEASE mode */
     714             :         }
     715        1811 :         (*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        1811 :     U8_TRACE_END_ERR( result );
     724        1811 :     return result;
     725             : }
     726             : 
     727        1488 : u8_error_t data_database_in_transaction_create ( data_database_t *this_, const char* sql_statement, data_row_t* out_new_id )
     728             : {
     729        1488 :     U8_TRACE_BEGIN();
     730        1488 :     assert( NULL != sql_statement );
     731        1488 :     u8_error_t result = U8_ERROR_NONE;
     732             :     int sqlite_err;
     733        1488 :     char *error_msg = NULL;
     734        1488 :     sqlite3 *const db = (*this_).db;
     735             : 
     736        1488 :     if ( data_database_is_open( this_ ) )
     737             :     {
     738        1488 :         U8_LOG_EVENT( "sqlite3_exec: sql_statement (see trace)" );
     739        1488 :         U8_TRACE_INFO_STR( "sqlite3_exec:", sql_statement );
     740        1488 :         sqlite_err = sqlite3_exec( db, sql_statement, NULL, NULL, &error_msg );
     741        1488 :         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        1476 :         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        1488 :         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        1488 :         if ( NULL != out_new_id )
     763             :         {
     764        1488 :             if ( SQLITE_OK == sqlite_err )
     765             :             {
     766             :                 data_row_t new_id;
     767        1476 :                 new_id = sqlite3_last_insert_rowid(db);
     768        1476 :                 U8_LOG_EVENT_INT( "sqlite3_last_insert_rowid():", new_id );
     769        1476 :                 *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        1488 :     U8_TRACE_END_ERR( result );
     781        1488 :     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         241 : u8_error_t data_database_add_db_listener( data_database_t *this_, data_database_listener_t *listener )
     834             : {
     835         241 :     U8_TRACE_BEGIN();
     836         241 :     assert( NULL != listener );
     837         241 :     u8_error_t result = U8_ERROR_NONE;
     838         241 :     bool already_registered = false;
     839             : 
     840         241 :     result |= data_database_lock_on_write( this_ );
     841             : 
     842         241 :     int pos = -1;
     843        5061 :     for( int index = 0; index < DATA_DATABASE_MAX_LISTENERS; index ++ )
     844             :     {
     845        4820 :         if ( NULL == (*this_).listener_list[index] )
     846             :         {
     847        4276 :             pos = index;
     848             :         }
     849        4820 :         if ( listener == (*this_).listener_list[index] )
     850             :         {
     851           1 :             already_registered = true;
     852             :         }
     853             :     }
     854             : 
     855         241 :     if ( already_registered )
     856             :     {
     857           1 :         U8_LOG_ERROR( "Listener already registered." );
     858           1 :         result |= U8_ERROR_INVALID_REQUEST;
     859             :     }
     860         240 :     else if ( -1 != pos )
     861             :     {
     862         239 :         (*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         241 :     result |= data_database_unlock_on_write( this_ );
     871             : 
     872         241 :     U8_TRACE_END_ERR( result );
     873         241 :     return result;
     874             : }
     875             : 
     876         220 : u8_error_t data_database_remove_db_listener( data_database_t *this_, data_database_listener_t *listener )
     877             : {
     878         220 :     U8_TRACE_BEGIN();
     879             : 
     880         220 :     assert( NULL != listener );
     881             : 
     882         220 :     u8_error_t result = U8_ERROR_NONE;
     883         220 :     int count_closed = 0;
     884             : 
     885         220 :     result |= data_database_lock_on_write( this_ );
     886             : 
     887        4620 :     for( int index = 0; index < DATA_DATABASE_MAX_LISTENERS; index ++ )
     888             :     {
     889        4400 :         if ( (*this_).listener_list[index] == listener )
     890             :         {
     891         219 :             (*this_).listener_list[index] = NULL;
     892         219 :             count_closed ++;
     893             :         }
     894             :     }
     895             : 
     896         220 :     result |= data_database_unlock_on_write( this_ );
     897             : 
     898         220 :     if ( count_closed == 0 )
     899             :     {
     900           1 :         U8_LOG_ERROR( "listener not found" );
     901           1 :         result |= U8_ERROR_INVALID_REQUEST;
     902             :     }
     903             : 
     904         220 :     U8_TRACE_END_ERR( result );
     905         220 :     return result;
     906             : }
     907             : 
     908         132 : u8_error_t data_database_private_notify_db_listeners( data_database_t *this_, data_database_listener_signal_t signal_id )
     909             : {
     910         132 :     U8_TRACE_BEGIN();
     911             :     data_database_listener_t *(listener_list_copy[DATA_DATABASE_MAX_LISTENERS]);
     912         132 :     u8_error_t result = U8_ERROR_NONE;
     913             : 
     914         132 :     result |= data_database_lock_on_write( this_ );
     915             : 
     916         132 :     memcpy( listener_list_copy, (*this_).listener_list, sizeof(listener_list_copy) );
     917             : 
     918         132 :     result |= data_database_unlock_on_write( this_ );
     919             : 
     920        2772 :     for( int index = 0; index < DATA_DATABASE_MAX_LISTENERS; index ++ )
     921             :     {
     922        2640 :         if ( NULL != listener_list_copy[index] )
     923             :         {
     924          49 :             data_database_listener_notify( listener_list_copy[index], signal_id );
     925             :         }
     926             :     }
     927             : 
     928         132 :     U8_TRACE_END_ERR( result );
     929         132 :     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 1.16