LCOV - code coverage report
Current view: top level - data/source/storage - data_database.c (source / functions) Hit Total Coverage
Test: crystal-facet-uml_v1.62.0_covts Lines: 237 318 74.5 %
Date: 2024-12-21 18:34:41 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             : /* ================================ Lifecycle ================================ */
     287             : 
     288          62 : void data_database_init ( data_database_t *this_ )
     289             : {
     290          62 :     U8_TRACE_BEGIN();
     291          62 :     U8_LOG_EVENT_INT( "compiled against sqlite3:    ", SQLITE_VERSION_NUMBER );
     292          62 :     U8_LOG_EVENT_STR( "linked to sqlite3_libversion:", sqlite3_libversion() );
     293             : 
     294          62 :     (*this_).db_file_name = utf8stringbuf_init( sizeof((*this_).private_db_file_name_buffer), (*this_).private_db_file_name_buffer );
     295          62 :     utf8stringbuf_clear( (*this_).db_file_name );
     296             : 
     297          62 :     g_mutex_init ( &((*this_).lock_on_write) );
     298          62 :     g_mutex_lock ( &((*this_).lock_on_write) );  /* (*this_).locked_on_write may only be changed when having the lock */
     299          62 :     (*this_).locked_on_write = false;
     300          62 :     g_mutex_unlock ( &((*this_).lock_on_write) );  /* this call manages a memory barrier that allows for cache coherence */
     301             : 
     302          62 :     u8_error_t result = data_database_lock_on_write( this_ );
     303             :     {
     304          62 :         (*this_).db_state = DATA_DATABASE_STATE_CLOSED;
     305          62 :         (*this_).transaction_recursion = 0;
     306          62 :         (*this_).revision = 0;
     307             :     }
     308          62 :     result |= data_database_unlock_on_write( this_ );
     309          62 :     if( result != U8_ERROR_NONE )
     310             :     {
     311           0 :         assert(false);
     312             :     }
     313             : 
     314          62 :     data_change_notifier_init ( &((*this_).notifier) );
     315          62 :     data_database_private_clear_db_listener_list( this_ );
     316             : 
     317          62 :     U8_TRACE_END();
     318          62 : }
     319             : 
     320          62 : u8_error_t data_database_private_open ( data_database_t *this_, const char* db_file_path, int sqlite3_flags )
     321             : {
     322          62 :     U8_TRACE_BEGIN();
     323          62 :     assert( NULL != db_file_path );
     324             :     /* there should not be pending transactions when calling open */
     325          62 :     assert( (*this_).transaction_recursion == 0 );
     326             :     int sqlite_err;
     327          62 :     u8_error_t result = U8_ERROR_NONE;
     328          62 :     bool notify_listeners = false;
     329             : 
     330          62 :     result |= data_database_lock_on_write( this_ );
     331             : 
     332          62 :     if ( (*this_).db_state != DATA_DATABASE_STATE_CLOSED )
     333             :     {
     334           0 :         U8_TRACE_INFO("data_database_open called on database that was not closed.");
     335           0 :         result |= U8_ERROR_INVALID_REQUEST;
     336             :     }
     337             :     else
     338             :     {
     339          62 :         utf8stringbuf_copy_str( (*this_).db_file_name, db_file_path );
     340             : 
     341          62 :         U8_LOG_EVENT_STR( "sqlite3_open_v2:", utf8stringbuf_get_string( (*this_).db_file_name ) );
     342          62 :         sqlite_err = sqlite3_open_v2( utf8stringbuf_get_string( (*this_).db_file_name ),
     343             :                                       &((*this_).db),
     344             :                                       sqlite3_flags,
     345             :                                       NULL
     346             :                                     );
     347          62 :         if ( SQLITE_OK != sqlite_err )
     348             :         {
     349           0 :             U8_LOG_ERROR_INT( "sqlite3_open_v2() failed:", sqlite_err );
     350           0 :             U8_LOG_ERROR_STR( "sqlite3_open_v2() failed:", utf8stringbuf_get_string( (*this_).db_file_name ) );
     351           0 :             (*this_).db_state = DATA_DATABASE_STATE_CLOSED;
     352           0 :             result |= U8_ERROR_NO_DB;  /* no db to use */
     353             :         }
     354             :         else
     355             :         {
     356             :             u8_error_t init_err;
     357          62 :             init_err = data_database_private_initialize_tables( this_ );
     358          62 :             if ( init_err == U8_ERROR_NONE )
     359             :             {
     360          62 :                 init_err = data_database_private_upgrade_tables( this_ );
     361             :             }
     362             : 
     363          62 :             if ( init_err == U8_ERROR_NONE )
     364             :             {
     365             :                 (*this_).db_state
     366         124 :                     = ( (sqlite3_flags & SQLITE_OPEN_MEMORY) == 0 )
     367             :                     ? DATA_DATABASE_STATE_OPEN
     368          62 :                     : DATA_DATABASE_STATE_IN_MEM;
     369          62 :                 notify_listeners = true;
     370             :             }
     371             :             else
     372             :             {
     373           0 :                 U8_LOG_EVENT_STR( "sqlite3_close:", utf8stringbuf_get_string( (*this_).db_file_name ) );
     374           0 :                 sqlite_err = sqlite3_close( (*this_).db );
     375           0 :                 if ( SQLITE_OK != sqlite_err )
     376             :                 {
     377           0 :                     U8_LOG_ERROR_INT( "sqlite3_close() failed:", sqlite_err );
     378             :                 }
     379           0 :                 utf8stringbuf_clear( (*this_).db_file_name );
     380           0 :                 (*this_).db_state = DATA_DATABASE_STATE_CLOSED;
     381             :             }
     382          62 :             result |= init_err;
     383             :         }
     384             :     }
     385             : 
     386          62 :     result |= data_database_unlock_on_write( this_ );
     387             : 
     388          62 :     if ( notify_listeners )
     389             :     {
     390             :         /* do sent notifications is active by default on a newly opened db */
     391          62 :         data_change_notifier_disable_stealth_mode( &((*this_).notifier) );
     392             : 
     393             :         /* inform readers and writers on open */
     394          62 :         result |= data_database_private_notify_db_listeners( this_, DATA_DATABASE_LISTENER_SIGNAL_DB_OPENED );
     395             : 
     396             :         /* inform listeners on changes */
     397          62 :         data_change_notifier_emit_signal( &((*this_).notifier),
     398             :                                           DATA_CHANGE_EVENT_TYPE_DB_OPENED,
     399             :                                           DATA_TABLE_VOID,
     400             :                                           DATA_ROW_VOID,
     401             :                                           DATA_TABLE_VOID,
     402             :                                           DATA_ROW_VOID
     403             :                                         );
     404             :     }
     405             : 
     406          62 :     U8_TRACE_END_ERR( result );
     407          62 :     return result;
     408             : }
     409             : 
     410          62 : u8_error_t data_database_close ( data_database_t *this_ )
     411             : {
     412          62 :     U8_TRACE_BEGIN();
     413             :     /* there should not be pending transactions when calling cloas */
     414          62 :     assert( (*this_).transaction_recursion == 0 );
     415             :     int sqlite_err;
     416          62 :     u8_error_t result = U8_ERROR_NONE;
     417          62 :     bool notify_change_listeners = false;
     418             : 
     419             :     /* do sent notifications before closing: */
     420          62 :     if ( data_database_is_open( this_ ) )
     421             :     {
     422             :         /* do sent notifications when closing: */
     423          62 :         data_change_notifier_disable_stealth_mode( &((*this_).notifier) );
     424             : 
     425             :         /* prepare close */
     426          62 :         data_change_notifier_emit_signal( &((*this_).notifier),
     427             :                                           DATA_CHANGE_EVENT_TYPE_DB_PREPARE_CLOSE,
     428             :                                           DATA_TABLE_VOID,
     429             :                                           DATA_ROW_VOID,
     430             :                                           DATA_TABLE_VOID,
     431             :                                           DATA_ROW_VOID
     432             :                                         );
     433             : 
     434             :         /* inform readers and writers on close */
     435          62 :         result |= data_database_private_notify_db_listeners( this_, DATA_DATABASE_LISTENER_SIGNAL_PREPARE_CLOSE );
     436             :     }
     437             : 
     438          62 :     result |= data_database_lock_on_write( this_ );
     439             : 
     440          62 :     if ( (*this_).db_state != DATA_DATABASE_STATE_CLOSED )
     441             :     {
     442             :         /* perform close */
     443          62 :         U8_LOG_EVENT_STR( "sqlite3_close:", utf8stringbuf_get_string( (*this_).db_file_name ) );
     444          62 :         sqlite_err = sqlite3_close( (*this_).db );
     445          62 :         if ( SQLITE_OK != sqlite_err )
     446             :         {
     447           0 :             U8_LOG_ERROR_INT( "sqlite3_close() failed:", sqlite_err );
     448           0 :             result |= U8_ERROR_AT_DB;
     449             :         }
     450             : 
     451          62 :         utf8stringbuf_clear( (*this_).db_file_name );
     452          62 :         (*this_).db_state = DATA_DATABASE_STATE_CLOSED;
     453          62 :         (*this_).transaction_recursion = 0;
     454             : 
     455          62 :         notify_change_listeners = true;
     456             :     }
     457             :     else
     458             :     {
     459           0 :         U8_TRACE_INFO("data_database_close called on database that was not open.");
     460           0 :         result |= U8_ERROR_INVALID_REQUEST;
     461             :     }
     462             : 
     463          62 :     result |= data_database_unlock_on_write( this_ );
     464             : 
     465          62 :     if ( notify_change_listeners )
     466             :     {
     467             :         /* inform listeners on changes */
     468          62 :         data_change_notifier_emit_signal( &((*this_).notifier),
     469             :                                           DATA_CHANGE_EVENT_TYPE_DB_CLOSED,
     470             :                                           DATA_TABLE_VOID,
     471             :                                           DATA_ROW_VOID,
     472             :                                           DATA_TABLE_VOID,
     473             :                                           DATA_ROW_VOID
     474             :         );
     475             :     }
     476             : 
     477          62 :     U8_TRACE_END_ERR( result );
     478          62 :     return result;
     479             : }
     480             : 
     481          62 : void data_database_destroy ( data_database_t *this_ )
     482             : {
     483          62 :     U8_TRACE_BEGIN();
     484             :     /* there should not be pending transactions when calling destroy */
     485          62 :     assert( (*this_).transaction_recursion == 0 );
     486             : 
     487          62 :     data_database_private_clear_db_listener_list( this_ );
     488          62 :     if ( data_database_is_open( this_ ) )
     489             :     {
     490           1 :         data_database_close( this_ );
     491             :     }
     492          62 :     data_change_notifier_destroy( &((*this_).notifier) );
     493             : 
     494          62 :     u8_error_t result = data_database_lock_on_write( this_ );
     495             :     {
     496          62 :         (*this_).transaction_recursion = 0;
     497             :     }
     498          62 :     result |= data_database_unlock_on_write( this_ );
     499          62 :     if( result != U8_ERROR_NONE )
     500             :     {
     501           0 :         assert(false);
     502             :     }
     503             : 
     504             :     /* g_mutex_clear ( &((*this_).lock_on_write) ); -- must not be called because this GMutex is not on the stack */
     505             : 
     506          62 :     U8_TRACE_END();
     507          62 : }
     508             : 
     509          62 : u8_error_t data_database_private_initialize_tables( data_database_t *this_ )
     510             : {
     511          62 :     U8_TRACE_BEGIN();
     512          62 :     u8_error_t result = U8_ERROR_NONE;
     513             : 
     514             :     /* since the database is locked during data_database_private_initialize_tables */
     515             :     /* we cannot call data_database_transaction_begin or data_database_in_transaction_execute here */
     516             : 
     517          62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_HEAD_TABLE, false );
     518          62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_CLASSIFIER_TABLE, false );
     519          62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_RELATIONSHIP_TABLE, false );
     520          62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_FEATURE_TABLE, false );
     521          62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_DIAGRAM_TABLE, false );
     522          62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_CREATE_DIAGRAMELEMENT_TABLE, false );
     523             : 
     524          62 :     U8_TRACE_END_ERR( result );
     525          62 :     return result;
     526             : }
     527             : 
     528          62 : u8_error_t data_database_private_upgrade_tables( data_database_t *this_ )
     529             : {
     530          62 :     U8_TRACE_BEGIN();
     531          62 :     u8_error_t result = U8_ERROR_NONE;
     532             : 
     533             :     /* update table classifiers from version 1.32.1 or earlier to later versions with diagram.display_flags */
     534          62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_DIAGRAM_TABLE_1, true );
     535             : 
     536             :     /* update all 5 tables from version 1.32.1 (no uuid) to later versions with uuid field */
     537          62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_CLASSIFIER_TABLE_UUID, true );
     538          62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_CLASSIFIER_UUID, true );
     539          62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_RELATIONSHIP_TABLE_UUID, true );
     540          62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_RELATIONSHIP_UUID, true );
     541          62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_FEATURE_TABLE_UUID, true );
     542          62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_FEATURE_UUID, true );
     543          62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_DIAGRAM_TABLE_UUID, true );
     544          62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_DIAGRAM_UUID, true );
     545          62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_DIAGRAMELEMENT_TABLE_UUID, true );
     546          62 :     result |= data_database_private_exec_sql( this_, DATA_DATABASE_UPDATE_DIAGRAMELEMENT_UUID, true );
     547             : 
     548             :     /* update table diagrams and relationships from version 1.46.0 or earlier to later versions with stereotype */
     549             :     /* do not care for "already existed" errors: */
     550          62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_RELATIONSHIP_TABLE_STEREOTYPE, true );
     551          62 :     data_database_private_exec_sql( this_, DATA_DATABASE_ALTER_DIAGRAM_TABLE_STEREOTYPE, true );
     552             : 
     553          62 :     if ( u8_error_contains( result, U8_ERROR_READ_ONLY_DB ) )
     554             :     {
     555           0 :         U8_LOG_EVENT( "sqlite3 database is read only." );
     556           0 :         result = u8_error_more_than( result, U8_ERROR_READ_ONLY_DB ) ? U8_ERROR_AT_DB : U8_ERROR_NONE;
     557             :     }
     558             : 
     559          62 :     U8_TRACE_END_ERR( result );
     560          62 :     return result;
     561             : }
     562             : 
     563             : /* ================================ Actions on DB ================================ */
     564             : 
     565           0 : u8_error_t data_database_flush_caches ( data_database_t *this_ )
     566             : {
     567           0 :     U8_TRACE_BEGIN();
     568           0 :     u8_error_t result = U8_ERROR_NONE;
     569             : 
     570           0 :     if ( data_database_is_open( this_ ) )
     571             :     {
     572           0 :         U8_LOG_EVENT_INT( "sqlite3_libversion_number()", sqlite3_libversion_number() );
     573           0 :         if ( sqlite3_libversion_number() >= 3010000 )
     574             :         {
     575             :             /* available if sqlite newer than 2016-01-06 (3.10.0) */
     576             : #if ( SQLITE_VERSION_NUMBER >= 3010000 )
     577             :             int sqlite_err;
     578           0 :             U8_LOG_EVENT( "sqlite3_db_cacheflush" );
     579           0 :             sqlite_err = sqlite3_db_cacheflush( (*this_).db );
     580           0 :             if ( SQLITE_OK != sqlite_err )
     581             :             {
     582           0 :                 U8_LOG_ERROR_INT( "sqlite3_db_cacheflush() failed:", sqlite_err );
     583           0 :                 result = U8_ERROR_AT_DB;
     584             :             }
     585             : #else
     586             :             U8_LOG_WARNING_INT( "The compile-time version of sqlite3 did not provide the sqlite3_db_cacheflush() function.",
     587             :                                 SQLITE_VERSION_NUMBER
     588             :                               );
     589             : #endif
     590             :         }
     591             :         else
     592             :         {
     593           0 :             U8_LOG_WARNING_INT( "The runtime-time version of sqlite3 does not provide the sqlite3_db_cacheflush() function.",
     594             :                                 sqlite3_libversion_number()
     595             :                               );
     596             :         }
     597             :     }
     598             :     else
     599             :     {
     600           0 :         result = U8_ERROR_NO_DB;
     601             :     }
     602             : 
     603           0 :     U8_TRACE_END_ERR( result );
     604           0 :     return result;
     605             : }
     606             : 
     607           0 : u8_error_t data_database_trace_stats ( data_database_t *this_ )
     608             : {
     609           0 :     U8_TRACE_BEGIN();
     610           0 :     u8_error_t result = U8_ERROR_NONE;
     611             : 
     612           0 :     if ( data_database_is_open( this_ ) )
     613             :     {
     614             :         sqlite3_int64 use;
     615             :         sqlite3_int64 max;
     616             : 
     617           0 :         use = sqlite3_memory_used();
     618           0 :         max = sqlite3_memory_highwater(false);
     619           0 :         U8_TRACE_INFO_INT_INT( "sqlite3_memory_used/highwater():", use, max );
     620             :     }
     621             :     else
     622             :     {
     623           0 :         U8_TRACE_INFO( "database not open." );
     624             :     }
     625             : 
     626           0 :     U8_TRACE_END_ERR( result );
     627           0 :     return result;
     628             : }
     629             : 
     630        1799 : u8_error_t data_database_transaction_begin ( data_database_t *this_ )
     631             : {
     632        1799 :     U8_TRACE_BEGIN();
     633             :     /* nesting of transactions should not be greater than 2. You may increase this limit if needed. */
     634        1799 :     assert( (*this_).transaction_recursion < 2 );
     635        1799 :     u8_error_t result = U8_ERROR_NONE;
     636             :     int sqlite_err;
     637        1799 :     char *error_msg = NULL;
     638        1799 :     sqlite3 *db = data_database_get_database_ptr( this_ );
     639             : 
     640        1799 :     if ( data_database_is_open( this_ ) )
     641             :     {
     642        1799 :         if ( (*this_).transaction_recursion == 0 )
     643             :         {
     644        1791 :             U8_LOG_EVENT_STR( "sqlite3_exec:", DATA_DATABASE_BEGIN_TRANSACTION );
     645        1791 :             sqlite_err = sqlite3_exec( db, DATA_DATABASE_BEGIN_TRANSACTION, NULL, NULL, &error_msg );
     646        1791 :             if ( SQLITE_OK != sqlite_err )
     647             :             {
     648           0 :                 U8_LOG_ERROR_STR( "sqlite3_exec() failed:", DATA_DATABASE_BEGIN_TRANSACTION );
     649           0 :                 U8_LOG_ERROR_INT( "sqlite3_exec() failed:", sqlite_err );
     650           0 :                 result |= U8_ERROR_AT_DB;
     651             :             }
     652        1791 :             if ( error_msg != NULL )
     653             :             {
     654           0 :                 U8_LOG_ERROR_STR( "sqlite3_exec() failed:", error_msg );
     655           0 :                 sqlite3_free( error_msg );
     656           0 :                 error_msg = NULL;
     657             :             }
     658             :         }
     659        1799 :         (*this_).transaction_recursion ++;
     660             :     }
     661             :     else
     662             :     {
     663           0 :         U8_LOG_WARNING_STR( "database not open. cannot execute", DATA_DATABASE_BEGIN_TRANSACTION );
     664           0 :         result = U8_ERROR_NO_DB;
     665             :     }
     666             : 
     667        1799 :     U8_TRACE_END_ERR( result );
     668        1799 :     return result;
     669             : }
     670             : 
     671        1799 : u8_error_t data_database_transaction_commit ( data_database_t *this_ )
     672             : {
     673        1799 :     U8_TRACE_BEGIN();
     674             :     /* there should be at least 1 pending transaction */
     675        1799 :     assert( (*this_).transaction_recursion > 0 );
     676        1799 :     u8_error_t result = U8_ERROR_NONE;
     677             :     int sqlite_err;
     678        1799 :     char *error_msg = NULL;
     679        1799 :     sqlite3 *db = data_database_get_database_ptr( this_ );
     680             : 
     681        1799 :     if ( data_database_is_open( this_ ) )
     682             :     {
     683        1799 :         if ( (*this_).transaction_recursion == 1 )
     684             :         {
     685        1791 :             U8_LOG_EVENT_STR( "sqlite3_exec:", DATA_DATABASE_COMMIT_TRANSACTION );
     686        1791 :             sqlite_err = sqlite3_exec( db, DATA_DATABASE_COMMIT_TRANSACTION, NULL, NULL, &error_msg );
     687        1791 :             if ( SQLITE_OK != sqlite_err )
     688             :             {
     689           0 :                 U8_LOG_ERROR_STR( "sqlite3_exec() failed:", DATA_DATABASE_COMMIT_TRANSACTION );
     690           0 :                 U8_LOG_ERROR_INT( "sqlite3_exec() failed:", sqlite_err );
     691           0 :                 result |= U8_ERROR_AT_DB;
     692             :             }
     693        1791 :             if ( error_msg != NULL )
     694             :             {
     695           0 :                 U8_LOG_ERROR_STR( "sqlite3_exec() failed:", error_msg );
     696           0 :                 sqlite3_free( error_msg );
     697           0 :                 error_msg = NULL;
     698             :             }
     699             : 
     700             :             /* increase the revision id */
     701             :             u8_error_t locking_error;
     702        1791 :             locking_error = data_database_lock_on_write( this_ );
     703        1791 :             (*this_).revision ++;
     704        1791 :             locking_error |= data_database_unlock_on_write( this_ );
     705        1791 :             assert( locking_error == U8_ERROR_NONE );
     706             :             (void) locking_error;  /* this should not happen in RELEASE mode */
     707             :         }
     708        1799 :         (*this_).transaction_recursion --;
     709             :     }
     710             :     else
     711             :     {
     712           0 :         U8_LOG_WARNING_STR( "database not open. cannot execute", DATA_DATABASE_COMMIT_TRANSACTION );
     713           0 :         result = U8_ERROR_NO_DB;
     714             :     }
     715             : 
     716        1799 :     U8_TRACE_END_ERR( result );
     717        1799 :     return result;
     718             : }
     719             : 
     720        1476 : u8_error_t data_database_in_transaction_create ( data_database_t *this_, const char* sql_statement, data_row_t* out_new_id )
     721             : {
     722        1476 :     U8_TRACE_BEGIN();
     723        1476 :     assert( NULL != sql_statement );
     724        1476 :     u8_error_t result = U8_ERROR_NONE;
     725             :     int sqlite_err;
     726        1476 :     char *error_msg = NULL;
     727        1476 :     sqlite3 *const db = (*this_).db;
     728             : 
     729        1476 :     if ( data_database_is_open( this_ ) )
     730             :     {
     731        1476 :         U8_LOG_EVENT( "sqlite3_exec: sql_statement (see trace)" );
     732        1476 :         U8_TRACE_INFO_STR( "sqlite3_exec:", sql_statement );
     733        1476 :         sqlite_err = sqlite3_exec( db, sql_statement, NULL, NULL, &error_msg );
     734        1476 :         if ( SQLITE_CONSTRAINT == (0xff & sqlite_err) )
     735             :         {
     736             :             /* This case happens if id is not unique and/or if a classifier name is not unique*/
     737          12 :             U8_LOG_ERROR( "sqlite3_exec() failed due to UNIQUE constraint: sql_statement (see trace)" );
     738          12 :             U8_TRACE_INFO_STR( "sqlite3_exec() failed due to UNIQUE constraint:", sql_statement );
     739          12 :             result |= U8_ERROR_DUPLICATE;
     740             :         }
     741        1464 :         else if ( SQLITE_OK != sqlite_err )
     742             :         {
     743           0 :             U8_LOG_ERROR( "sqlite3_exec() failed: sql_statement (see trace)" );
     744           0 :             U8_LOG_ERROR_INT( "sqlite3_exec() failed:", sqlite_err );
     745           0 :             U8_TRACE_INFO_STR( "sqlite3_exec:", sql_statement );
     746           0 :             result |= (sqlite_err == SQLITE_READONLY) ? U8_ERROR_READ_ONLY_DB : U8_ERROR_AT_DB;
     747             :         }
     748        1476 :         if ( error_msg != NULL )
     749             :         {
     750          12 :             U8_LOG_ERROR_STR( "sqlite3_exec() failed:", error_msg );
     751          12 :             sqlite3_free( error_msg );
     752          12 :             error_msg = NULL;
     753             :         }
     754             : 
     755        1476 :         if ( NULL != out_new_id )
     756             :         {
     757        1476 :             if ( SQLITE_OK == sqlite_err )
     758             :             {
     759             :                 data_row_t new_id;
     760        1464 :                 new_id = sqlite3_last_insert_rowid(db);
     761        1464 :                 U8_LOG_EVENT_INT( "sqlite3_last_insert_rowid():", new_id );
     762        1464 :                 *out_new_id = new_id;
     763             :             }
     764             :         }
     765             :     }
     766             :     else
     767             :     {
     768           0 :         U8_LOG_WARNING( "database not open. cannot execute sql_statement (see trace)" );
     769           0 :         U8_TRACE_INFO_STR( "database not open. cannot execute", sql_statement );
     770           0 :         result = U8_ERROR_NO_DB;
     771             :     }
     772             : 
     773        1476 :     U8_TRACE_END_ERR( result );
     774        1476 :     return result;
     775             : }
     776             : 
     777         316 : u8_error_t data_database_in_transaction_execute ( data_database_t *this_, const char* sql_statement )
     778             : {
     779         316 :     U8_TRACE_BEGIN();
     780         316 :     assert( NULL != sql_statement );
     781         316 :     u8_error_t result = U8_ERROR_NONE;
     782             :     int sqlite_err;
     783         316 :     char *error_msg = NULL;
     784         316 :     sqlite3 *db = (*this_).db;
     785             : 
     786         316 :     if ( data_database_is_open( this_ ) )
     787             :     {
     788         316 :         U8_LOG_EVENT( "sqlite3_exec: sql_statement (see trace)" );
     789         316 :         U8_TRACE_INFO_STR( "sqlite3_exec:", sql_statement );
     790         316 :         sqlite_err = sqlite3_exec( db, sql_statement, NULL, NULL, &error_msg );
     791         316 :         if ( SQLITE_CONSTRAINT == (0xff & sqlite_err) )
     792             :         {
     793           0 :             U8_LOG_ERROR( "sqlite3_exec() failed due to UNIQUE constraint: sql_statement (see trace)" );
     794           0 :             U8_TRACE_INFO_STR( "sqlite3_exec() failed due to UNIQUE constraint:", sql_statement );
     795           0 :             result |= U8_ERROR_DUPLICATE_NAME;
     796             :         }
     797         316 :         else if ( SQLITE_OK != sqlite_err )
     798             :         {
     799           0 :             U8_LOG_ERROR( "sqlite3_exec() failed: sql_statement (see trace)" );
     800           0 :             U8_LOG_ERROR_INT( "sqlite3_exec() failed:", sqlite_err );
     801           0 :             U8_TRACE_INFO_STR( "sqlite3_exec() failed:", sql_statement );
     802           0 :             result |= (sqlite_err == SQLITE_READONLY) ? U8_ERROR_READ_ONLY_DB : U8_ERROR_AT_DB;
     803             :         }
     804         316 :         if ( error_msg != NULL )
     805             :         {
     806           0 :             U8_LOG_ERROR_STR( "sqlite3_exec() failed:", error_msg );
     807           0 :             sqlite3_free( error_msg );
     808           0 :             error_msg = NULL;
     809             :         }
     810             :     }
     811             :     else
     812             :     {
     813           0 :         U8_LOG_WARNING( "database not open. cannot execute sql_statement (see trace)" );
     814           0 :         U8_TRACE_INFO_STR( "database not open. cannot execute", sql_statement );
     815           0 :         result = U8_ERROR_NO_DB;
     816             :     }
     817             : 
     818         316 :     U8_TRACE_END_ERR( result );
     819         316 :     return result;
     820             : }
     821             : 
     822             : /* ================================ Information ================================ */
     823             : 
     824             : /* ================================ Change Listener ================================ */
     825             : 
     826         235 : u8_error_t data_database_add_db_listener( data_database_t *this_, data_database_listener_t *listener )
     827             : {
     828         235 :     U8_TRACE_BEGIN();
     829         235 :     assert( NULL != listener );
     830         235 :     u8_error_t result = U8_ERROR_NONE;
     831         235 :     bool already_registered = false;
     832             : 
     833         235 :     result |= data_database_lock_on_write( this_ );
     834             : 
     835         235 :     int pos = -1;
     836        4935 :     for( int index = 0; index < DATA_DATABASE_MAX_LISTENERS; index ++ )
     837             :     {
     838        4700 :         if ( NULL == (*this_).listener_list[index] )
     839             :         {
     840        4162 :             pos = index;
     841             :         }
     842        4700 :         if ( listener == (*this_).listener_list[index] )
     843             :         {
     844           1 :             already_registered = true;
     845             :         }
     846             :     }
     847             : 
     848         235 :     if ( already_registered )
     849             :     {
     850           1 :         U8_LOG_ERROR( "Listener already registered." );
     851           1 :         result |= U8_ERROR_INVALID_REQUEST;
     852             :     }
     853         234 :     else if ( -1 != pos )
     854             :     {
     855         233 :         (*this_).listener_list[pos] = listener;
     856             :     }
     857             :     else
     858             :     {
     859           1 :         U8_LOG_ERROR_INT( "Maximum number of listeners reached.", DATA_DATABASE_MAX_LISTENERS );
     860           1 :         result |= U8_ERROR_ARRAY_BUFFER_EXCEEDED;
     861             :     }
     862             : 
     863         235 :     result |= data_database_unlock_on_write( this_ );
     864             : 
     865         235 :     U8_TRACE_END_ERR( result );
     866         235 :     return result;
     867             : }
     868             : 
     869         214 : u8_error_t data_database_remove_db_listener( data_database_t *this_, data_database_listener_t *listener )
     870             : {
     871         214 :     U8_TRACE_BEGIN();
     872             : 
     873         214 :     assert( NULL != listener );
     874             : 
     875         214 :     u8_error_t result = U8_ERROR_NONE;
     876         214 :     int count_closed = 0;
     877             : 
     878         214 :     result |= data_database_lock_on_write( this_ );
     879             : 
     880        4494 :     for( int index = 0; index < DATA_DATABASE_MAX_LISTENERS; index ++ )
     881             :     {
     882        4280 :         if ( (*this_).listener_list[index] == listener )
     883             :         {
     884         213 :             (*this_).listener_list[index] = NULL;
     885         213 :             count_closed ++;
     886             :         }
     887             :     }
     888             : 
     889         214 :     result |= data_database_unlock_on_write( this_ );
     890             : 
     891         214 :     if ( count_closed == 0 )
     892             :     {
     893           1 :         U8_LOG_ERROR( "listener not found" );
     894           1 :         result |= U8_ERROR_INVALID_REQUEST;
     895             :     }
     896             : 
     897         214 :     U8_TRACE_END_ERR( result );
     898         214 :     return result;
     899             : }
     900             : 
     901         128 : u8_error_t data_database_private_notify_db_listeners( data_database_t *this_, data_database_listener_signal_t signal_id )
     902             : {
     903         128 :     U8_TRACE_BEGIN();
     904             :     data_database_listener_t *(listener_list_copy[DATA_DATABASE_MAX_LISTENERS]);
     905         128 :     u8_error_t result = U8_ERROR_NONE;
     906             : 
     907         128 :     result |= data_database_lock_on_write( this_ );
     908             : 
     909         128 :     memcpy( listener_list_copy, (*this_).listener_list, sizeof(listener_list_copy) );
     910             : 
     911         128 :     result |= data_database_unlock_on_write( this_ );
     912             : 
     913        2688 :     for( int index = 0; index < DATA_DATABASE_MAX_LISTENERS; index ++ )
     914             :     {
     915        2560 :         if ( NULL != listener_list_copy[index] )
     916             :         {
     917          49 :             data_database_listener_notify( listener_list_copy[index], signal_id );
     918             :         }
     919             :     }
     920             : 
     921         128 :     U8_TRACE_END_ERR( result );
     922         128 :     return result;
     923             : }
     924             : 
     925             : /* ================================ Lifecycle Lock ================================ */
     926             : 
     927             : 
     928             : /*
     929             : Copyright 2016-2024 Andreas Warnke
     930             : 
     931             : Licensed under the Apache License, Version 2.0 (the "License");
     932             : you may not use this file except in compliance with the License.
     933             : You may obtain a copy of the License at
     934             : 
     935             :     http://www.apache.org/licenses/LICENSE-2.0
     936             : 
     937             : Unless required by applicable law or agreed to in writing, software
     938             : distributed under the License is distributed on an "AS IS" BASIS,
     939             : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     940             : See the License for the specific language governing permissions and
     941             : limitations under the License.
     942             : */

Generated by: LCOV version 1.16