Script de création de base de données compatible SQLite
	
	
		Bonjour à tous,
J'ai modifié le script de création de la base de donnée de façon à ce qu'il soit compatible avec SQLite.
Voici ce qu'il y avait avant:
(au passage, il y a une erreur dans le fetch, il est indiqué le champ "id=" au lieu de "name="):
	Code:
	
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 
 | 
void updateDatabaseVersion()
{
  try
  {
    int domainVersion = qApp->property("DomainVersion").toInt();
    // On se connecte avec un utilisateur de la base de données qui a les droits de modifications du schéma
    QSqlDatabase db = qx::QxSqlDatabase::getSingleton()->getDatabaseCloned();
    db.setUserName("MyAdminLogin");
    db.setPassword("MyAdminPassword");
    // On s'assure que la session démarre une transaction et lève une exception à la moindre erreur
    qx::QxSession session(db, true, true);
    // On "fetch" la version de la base de données avec un verrou pour éviter les modifications concurrentes !
    // Si plusieurs utilisateurs lancent l'application en même temps et qu'une mise à jour
    // est nécessaire, le premier fera la mise à jour, et les autres seront en attente
    DatabaseVersion dbVersion;
    session.fetchByQuery(qx_query("WHERE id='MyAppName' FOR UPDATE"), dbVersion);
    // Pour les autres utilisateurs, une fois le verrou levé, on vérifie si la mise à jour est toujours nécessaire
    if (dbVersion.version >= domainVersion) { return; }
    // On exécute chaque instruction SQL avec la variable "query"
    QSqlQuery query(db);
    // On récupère toutes les classes persistantes C++ enregistrées dans le contexte QxOrm
    qx::QxCollection<QString, qx::IxClass *> * pAllClasses = qx::QxClassX::getAllClasses();
    if (! pAllClasses) { qAssert(false); return; }
    // on récupère la liste des tables existantes dans la base (fonction de Qt)
    QStringList tables = db.tables();
    for (long k = 0; k < pAllClasses->count(); k++)
    {
      qx::IxClass * pClass = pAllClasses->getByIndex(k);
      if (! pClass) { continue; }
      // Filtre les classes non persistantes
      if (pClass->isKindOf("qx::service::IxParameter") || pClass->isKindOf("qx::service::IxService")) { continue; }
      // Filtre les classes à jour : si la version de pClass est <= à la version enregistrée dans la base, la mise à jour n'est pas nécessaire
      if (pClass->getVersion() <= dbVersion.version) { continue; }
      // On crée la table si elle n'existe pas, et on définit son propriétaire
      if (! tables.contains(pClass->getName()))
      {
        query.exec("CREATE TABLE " + pClass->getName() + " ( ) WITH (OIDS = FALSE);"
                   "ALTER TABLE " + pClass->getName() + " OWNER TO \"MyAdminLogin\";");
        session += query.lastError();
      }
      // On ajoute les colonnes à la table si elles n'existent pas
      qx::IxDataMemberX * pDataMemberX = pClass->getDataMemberX();
      for (long l = 0; (pDataMemberX && (l < pDataMemberX->count_WithDaoStrategy())); l++)
      {
        qx::IxDataMember * p = pDataMemberX->get_WithDaoStrategy(l);
        if (! p || (p->getVersion() <= dbVersion.version)) { continue; }
        query.exec("ALTER TABLE " + pClass->getName() + " ADD COLUMN " + p->getName() + " " + p->getSqlType() + ";");
        session += query.lastError();
        if (p->getIsPrimaryKey()) // PRIMARY KEY
        {
          query.exec("ALTER TABLE " + pClass->getName() + " ADD PRIMARY KEY (" + p->getName() + ");");
          session += query.lastError();
        }
        if (p->getAllPropertyBagKeys().contains("INDEX")) // INDEX
        {
          query.exec("CREATE INDEX " + pClass->getName() + "_" + p->getName() + "_idx" + 
                     " ON " + pClass->getName() + " USING " + p->getPropertyBag("INDEX").toString() + " (" + p->getName() + ");");
          session += query.lastError();
        }
        if (p->getNotNull()) // NOT NULL
        {
          query.exec("ALTER TABLE " + pClass->getName() + " ALTER COLUMN " + p->getName() + " SET NOT NULL;");
          session += query.lastError();
        }
        if (p->getAutoIncrement()) // AUTO INCREMENT
        {
          query.exec("CREATE SEQUENCE " + pClass->getName() + "_" + p->getName() + "_seq" + "; "
                     "ALTER TABLE " + pClass->getName() + "_" + p->getName() + "_seq" + " OWNER TO \"MyAdminLogin\"; "
                     "ALTER TABLE " + pClass->getName() + " ALTER COLUMN " + p->getName() + " " +
                     "SET DEFAULT nextval('" + pClass->getName() + "_" + p->getName() + "_seq" + "'::regclass);");
          session += query.lastError();
        }
        if (p->getDescription() != "") // DESCRIPTION
        {
          // $$ceci est un texte ne nécessitant pas de caractères d'échappement dans postgres grace aux doubles dolars$$
          query.exec("COMMENT ON COLUMN " + pClass->getName() + "." + p->getName() + " IS $$" + p->getDescription() + "$$ ;");
          session += query.lastError();
        }
      }
    }
    // On enregistre la version courante de la base de données
    dbVersion.version = domainVersion;
    session.save(dbVersion);
    // Fin du block "try" : la session est détruite => commit ou rollback automatique
    // De plus, un commit ou rollback sur la transaction lève automatiquement le verrou posé précédemment
  }
  catch (const qx::dao::sql_error & err)
  {
    QSqlError sqlError = err.get();
    qDebug() << sqlError.databaseText();
    qDebug() << sqlError.driverText();
    qDebug() << sqlError.number();
    qDebug() << sqlError.type();
  }
} | 
 Et voici une version compatible avec SQLite:
	Code:
	
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 
 | void updateDatabaseVersion()
{
    try {
        int domainVersion = qApp->property( "DomainVersion" ).toInt();
 
        // We connect to the database with an user that has rights for the database creation
        QSqlDatabase db = qx::QxSqlDatabase::getSingleton()->getDatabaseCloned();
        db.setUserName( "root" );
        db.setPassword( "" );
        const bool isSQLite = db.driverName().contains( "SQLITE" );
 
        // Create a transaction that will throw exception on errors
        qx::QxSession session( db, true, true );
 
        // Fetch database version that will lock concurrent access (when multiple users starts the application at the same time)
        DatabaseVersion dbVersion;
        try
        {
            session.fetchByQuery( qx_query( QString( "WHERE name='") + kAppName + ( isSQLite ? "'" : QString("' FOR UPDATE") ) ), dbVersion );
            // For other users, when the locking will be over, we check if the update is still needed
            if ( dbVersion.version >= domainVersion ) { return; }
        }
        catch ( const qx::dao::sql_error & )
        {
            dbVersion.name = kAppName;
            dbVersion.version = -1;                 // Create tables
        }
 
        // Execute each query with this object
        QSqlQuery query( db );
 
        // On récupère toutes les classes persistantes C++ enregistrées dans le contexte QxOrm
        qx::QxCollection<QString, qx::IxClass *> * pAllClasses = qx::QxClassX::getAllClasses();
        if ( !pAllClasses ) { qAssert(false); return; }
 
        // Get database tables
        QStringList tables = db.tables();
 
        for ( long k = 0; k < pAllClasses->count(); k++ )
        {
            qx::IxClass * pClass = pAllClasses->getByIndex( k );
            if ( !pClass ) { continue; }
 
            // Filter non persistant classes
            if ( pClass->isKindOf( "qx::service::IxParameter" ) || pClass->isKindOf( "qx::service::IxService" ) ) { continue; }
 
            if (pClass->getVersion() <= dbVersion.version) { continue; }
 
            // We create tables if not already existing, and we define its owner
            if ( !tables.contains( pClass->getName() ) )
            {
                // Create attribute query query section
                QString attributes;
                qx::IxDataMemberX * pDataMemberX = pClass->getDataMemberX();
                for ( long l = 0; (pDataMemberX && (l < pDataMemberX->count_WithDaoStrategy())); l++ )
                {
                    qx::IxDataMember * p = pDataMemberX->get_WithDaoStrategy(l);
                    attributes += p->getName() + " " + p->getSqlType();
                    if (p->getNotNull()) // NOT NULL
                    {
                        attributes += " NOT NULL";
                    }
                    if (p->getIsPrimaryKey()) // PRIMARY KEY
                    {
                        attributes += " PRIMARY KEY";
                    }
                    if (p->getAutoIncrement()) // AUTO INCREMENT
                    {
                        attributes += " AUTOINCREMENT";
                    }
                    if ( l < pDataMemberX->count_WithDaoStrategy() - 1 )
                    {
                        attributes += ", ";
                    }
                }
 
                attributes = QString( " (" ) + attributes + ")";
                if ( !isSQLite )
                {
                    attributes += "  WITH ( OIDS = FALSE )";
                }
                query.exec( "CREATE TABLE " + pClass->getName() + attributes + ";"
                            "ALTER TABLE " + pClass->getName() + " OWNER TO \"root\";" );
                session += query.lastError();
 
                // Create sequences & index
                for ( long l = 0; (pDataMemberX && ( l < pDataMemberX->count_WithDaoStrategy() ) ); l++ )
                {
                    qx::IxDataMember * p = pDataMemberX->get_WithDaoStrategy(l);
 
                    if ( p->getAllPropertyBagKeys().contains( "INDEX" ) ) // INDEX
                    {
                        query.exec( "CREATE INDEX " + pClass->getName() + "_" + p->getName() + "_idx" + 
                                    " ON " + pClass->getName() + " USING " + p->getPropertyBag("INDEX").toString() + " (" + p->getName() + ");" );
                        session += query.lastError();
                    }
 
                    if (p->getAutoIncrement() && !isSQLite ) // AUTO INCREMENT
                    {
                        query.exec("CREATE SEQUENCE " + pClass->getName() + "_" + p->getName() + "_seq" + "; "
                                    "ALTER TABLE " + pClass->getName() + "_" + p->getName() + "_seq" + " OWNER TO \"root\"; "
                                    "ALTER TABLE " + pClass->getName() + " ALTER COLUMN " + p->getName() + " " +
                                    "SET DEFAULT nextval('" + pClass->getName() + "_" + p->getName() + "_seq" + "'::regclass);");
                        session += query.lastError();
                    }
 
                }
            }
            else
            {
                // We add columns to table if they don't exists
                qx::IxDataMemberX * pDataMemberX = pClass->getDataMemberX();
                for ( long l = 0; (pDataMemberX && (l < pDataMemberX->count_WithDaoStrategy())); l++ )
                {
                    qx::IxDataMember * p = pDataMemberX->get_WithDaoStrategy(l);
                    if (! p || (p->getVersion() <= dbVersion.version)) { continue; }
 
                    query.exec( "ALTER TABLE " + pClass->getName() + " ADD COLUMN " + p->getName() + " " + p->getSqlType() + ";" );
                    session += query.lastError();
 
                    if ( p->getIsPrimaryKey() ) // PRIMARY KEY
                    {
                        query.exec( "ALTER TABLE " + pClass->getName() + " ADD PRIMARY KEY (" + p->getName() + ");" );
                        session += query.lastError();
                    }
 
                    if ( p->getAllPropertyBagKeys().contains("INDEX") ) // INDEX
                    {
                        query.exec("CREATE INDEX " + pClass->getName() + "_" + p->getName() + "_idx" + 
                                    " ON " + pClass->getName() + " USING " + p->getPropertyBag("INDEX").toString() + " (" + p->getName() + ");");
                        session += query.lastError();
                    }
 
                    if (p->getNotNull()) // NOT NULL
                    {
                        query.exec("ALTER TABLE " + pClass->getName() + " ALTER COLUMN " + p->getName() + " SET NOT NULL;");
                        session += query.lastError();
                    }
 
                    if (p->getAutoIncrement()  && !isSQLite ) // AUTO INCREMENT
                    {
                        query.exec("CREATE SEQUENCE " + pClass->getName() + "_" + p->getName() + "_seq" + "; "
                                    "ALTER TABLE " + pClass->getName() + "_" + p->getName() + "_seq" + " OWNER TO \"root\"; "
                                    "ALTER TABLE " + pClass->getName() + " ALTER COLUMN " + p->getName() + " " +
                                    "SET DEFAULT nextval('" + pClass->getName() + "_" + p->getName() + "_seq" + "'::regclass);");
                        session += query.lastError();
                    }
 
                    if (p->getDescription() != "") // DESCRIPTION
                    {
                        query.exec("COMMENT ON COLUMN " + pClass->getName() + "." + p->getName() + " IS $$" + p->getDescription() + "$$ ;");
                        session += query.lastError();
                    }
                }
            }
        }
 
        // Update database version
        dbVersion.version = domainVersion;
        try
        {
            session.save( dbVersion );
        }
        catch (const qx::dao::sql_error & err)
        {
            session.insert( dbVersion );
        }
        // End of "try" : Session is destroyed => automatic commit or rollback
        // commit or rollback will unlock locked table (not for SQLite)
    }
    catch (const qx::dao::sql_error & err)
    {
        QSqlError sqlError = err.get();
        qDebug() << sqlError.databaseText();
        qDebug() << sqlError.driverText();
        qDebug() << sqlError.number();
        qDebug() << sqlError.type();
    }
} | 
 
Si vous avez des suggestions, je suis preneur... J'ai simplement rendu le script fonctionnel avec SQLite, mais je ne connais pas très bien SQL donc je ne sais pas si c'est optimal.
En gros, voici ce qui ne passait pas:
- SELECT .... FOR UPDATE -> passe pas sous SQLite, j'ai l'impression que le locking n'est pas supporté par SQLite.
- query.exec("CREATE TABLE " + pClass->getName() + " ( ) WITH (OIDS = FALSE);" -> la création de table sans champs ne passe pas... Le WITH (OIDS = FALSE) non plus.
- CREATE SEQUENCE -> non supporté à priori. Tout semble être automatique au niveau des séquences (à vérifier).