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:
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:
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).