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 : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
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 : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
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).