Files
qt5-legatus-legacy/Lts/Tools/DBAdministration/exercise_table_generator.cpp
T
2026-08-13 18:33:10 +02:00

792 lines
27 KiB
C++
Executable File

/**
* EXM Database Administartion (Create Schema/Tables/Views etc)
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2015-09-15
*/
#include "exercise_table_generator.h"
#include <ngldb/nsqlquery.h>
#include <ngldb/dbtransaction.h>
#include <QVariant>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QSqlError>
#include <QTextStream>
namespace DBAdministration
{
ExerciseTableGenerator::ExerciseTableGenerator(NGL::INGLLogger &logger, const QString &xmlPath, QObject *parent)
: QObject(parent)
, m_xmlPath(xmlPath)
, m_ownSchemaName(QLatin1String("exm"))
, m_logger(logger)
{
}
// --------------------------------------------------------------------------------------------------------
ExerciseTableGenerator::~ExerciseTableGenerator()
{
qDeleteAll(m_sqlGeneratorMap);
}
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::existsSchema(const QString &dbSchema, const QSqlDatabase &dbConn) const
{
NSqlQuery q(dbConn);
// Does service schema exist?
QString stmt = QLatin1String("SELECT EXISTS(SELECT 1 FROM information_schema.schemata") +
QLatin1String(" WHERE schema_name = '") + dbSchema + QLatin1String("')");
if (!q.execAndFetch(stmt))
return false;
return q.value(0).toBool();
}
// --------------------------------------------------------------------------------------------------------
//bool ExerciseTableGenerator::createDbSchemaIfNotExists(const QString &dbSchema) const
//{
// // TODO: make product-dependent
// QString dbUser = QLatin1String("ets_") + dbSchema;
// return createDbSchemaIfNotExists(dbSchema, dbUser);
//}
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::createDbSchemaIfNotExists(const QString &dbSchema, QString dbUser) const
{
DEBUG_LOG(m_logger) << "createDbSchema: dbSchema = " << dbSchema << ", dbUser = " << dbUser;
if (existsSchema(dbSchema, m_database))
return true;
// Schema doesn't exist => create
QString stmt = QLatin1String("CREATE SCHEMA IF NOT EXISTS ") + dbSchema;
DEBUG_LOG(m_logger) << "# " << stmt;
NSqlQuery q(m_database);
if (!q.exec(stmt))
{
ERROR_LOG(m_logger) << "Can't create schema '" << dbSchema << "' : " << q.getLastSqlError();
return false;
}
// Create specific user for schema (if exercise-related)
if (dbSchema.startsWith(QLatin1String("ex_")))
{
dbUser = QStringLiteral("ets_ex_00");
stmt = QLatin1String("SELECT create_user_if_not_exists('") + dbUser + QLatin1String("');");
DEBUG_LOG(m_logger) << "# " << stmt;
// Use stored procedure (=> create user only if not exists)
if (!q.exec(stmt))
return false;
// Add user to group <product>_ro: not needed anymore, it is included in stored procedure
// stmt = QLatin1String(" GRANT ets_ro TO ") + dbUser;
// DEBUG_LOG(m_logger) << "# " << stmt;
// if (!q.exec(stmt))
// return false;
}
// TODO: make dba user & db groups depend on product => Check/product Name needed somewhere: lts-cc.conf?
// HR: 09.03.2016
stmt = QLatin1String("GRANT USAGE ON SCHEMA ") + dbSchema +
QLatin1String(" TO ets_ro, ets_admin");
DEBUG_LOG(m_logger) << "# " << stmt;
if (!q.exec(stmt))
return false;
// create db_version table needed later for DB Updates
QString stmtDbVersion = QLatin1String("CREATE TABLE IF NOT EXISTS ") + dbSchema + QLatin1String(".db_version (")+
QLatin1String("id serial, ")+
QLatin1String("description text, ")+
QLatin1String("version text, ")+
QLatin1String("updated bigint DEFAULT (extract(epoch from now()) * 1000), ")+
QLatin1String("success boolean DEFAULT true, ")+
QLatin1String("message text);");
stmtDbVersion += QLatin1String("\nINSERT INTO ") + dbSchema + QLatin1String(".db_version (description, version) VALUES ('Create db_version table for all existent DB schemas', '1.00')");
if (!q.exec(stmtDbVersion))
WARNING_LOG(m_logger) << "DB: Could not create db_version table for schema " << dbSchema << "! Error code: " << q.lastError().nativeErrorCode();
else
INFO_LOG(m_logger) << "DB: created db_version table for schema " << dbSchema;
INFO_LOG(m_logger) << "########## DbSchema " << dbSchema << " created with corresponding rights.";
return true;
}
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::dropDbSchema(const QString &dbSchema, const QString &dbUser) const
{
// Drop Schema
QString stmt = QLatin1String("DROP SCHEMA IF EXISTS ") + dbSchema + QLatin1String(" CASCADE");
DEBUG_LOG(m_logger) << "# " << stmt;
NSqlQuery q(m_database);
if (!q.exec(stmt))
{
ERROR_LOG(m_logger) << "Can't drop schema '" << dbSchema << "' : " << q.getLastSqlError();
return false;
}
// drop only "old" users: ets_ex_42 etc
if (!dbUser.isEmpty() && dbUser != QStringLiteral("ets_ex_00"))
{
// Drop related user
stmt = QLatin1String("DROP USER ") + dbUser;
if (!q.exec(stmt))
WARNING_LOG(m_logger) << "Can't drop user '" << dbUser << "' : " << q.getLastSqlError();
}
return true;
}
/**
* @brief Checks if table exists / is available for dbUser
* (dbUser has the right to see it)
* @param tableName
* @param dbSchema
* @return true on success
*/
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::existsTable(const QString &tableName, const QString &dbSchema) const
{
QString stmt = QLatin1String("SELECT EXISTS ("
" SELECT 1 FROM information_schema.tables "
" WHERE table_schema = '") + dbSchema + QLatin1String("'"
" AND table_name = '") + tableName + QLatin1String("')");
DEBUG_LOG(m_logger) << "stmt: " << stmt;
NSqlQuery q(m_database);
if (!q.execAndFetch(stmt))
{
INFO_LOG(m_logger) << "Table " << tableName << " does not exist in schema " << dbSchema;
return false;
}
return q.value(0).toBool();
}
/**
* @brief ExerciseTableGenerator::init
* @param createOwnSchema
* @return
*/
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::init(const QSqlDatabase &database, const QString &ownSchemaName, bool createOwnSchema)
{
m_database = database;
m_ownSchemaName = ownSchemaName;
if (!createOwnSchema && !existsSchema(m_ownSchemaName, m_database))
{
ERROR_LOG(m_logger) << "Schema of the service (" << m_ownSchemaName << ") doesn't exist, aborting." ;
return false;
}
resetSearchPath();
// Read all xmlFiles in xmlPath
DEBUG_LOG(m_logger) << "xmlPath: " << m_xmlPath;
QFileInfo fi(m_xmlPath);
if (!fi.isDir())
{
ERROR_LOG(m_logger) << "Given xml path '" << m_xmlPath << "' is not a directory";
return false;
}
QDir xmlDir(m_xmlPath);
// Read xmlFiles only
QStringList filters;
filters << QLatin1String("*.xml");
xmlDir.setNameFilters(filters);
qDeleteAll(m_sqlGeneratorMap);
m_sqlGeneratorMap.clear();
QDirIterator dirIt(xmlDir);
while (dirIt.hasNext())
{
const QString xmlFile = dirIt.next();
DEBUG_LOG(m_logger) << "xmlFile: " << xmlFile;
// Init sqlGenerator for xmlFile
CodeGen::SqlGenerator *sqlGen = new CodeGen::SqlGenerator(xmlFile);
if (sqlGen->generateData(0) && sqlGen->isValid())
m_sqlGeneratorMap.insert(sqlGen->getServiceAbbr(), sqlGen);
else
delete sqlGen;
}
DEBUG_LOG(m_logger) << "sqlGen mapSize: " << m_sqlGeneratorMap.size();
emit aliveSignal();
if (createOwnSchema)
{
// Create Schema exm (i.e. for testing)
// TODO: make product-dependent
QString dbUser = QLatin1String("ets_exm");
if (!createDbSchemaIfNotExists(m_ownSchemaName, dbUser))
return false;
if (!executeSqlTableRelated(QStringLiteral("exm"), m_ownSchemaName, dbUser))
return false;
}
return true;
}
void ExerciseTableGenerator::close()
{
m_database = QSqlDatabase();
}
// Write sql-stmts for a service into a file
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::writeServiceSqlToFile(const QString &svcName, const QString &dbSchema)
{
if (!m_sqlGeneratorMap.contains(svcName))
return false;
DEBUG_LOG(m_logger) << "Write sql for service '" << svcName
<< "' with schema '" << dbSchema << "'";
CodeGen::SqlGenerator *sqlGen = m_sqlGeneratorMap.value(svcName);
// do not write to share/Lts/sql to not overwrite the generated files from codegen
QString fullSqlName = QDir::tempPath() + QLatin1Char('/') + svcName + QLatin1Char('_') + dbSchema + QLatin1String(".sql");
if (!sqlGen->writeSqlToFile(dbSchema, fullSqlName, CodeGen::ExerciseDependency::Exercise))
{
ERROR_LOG(m_logger) << "Failed to write sql-stmt to file "
<< fullSqlName;
return false;
}
DEBUG_LOG(m_logger) << "## => Written sql-stmt to '" << fullSqlName << "'";
return true;
}
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::executeSql(const QString &svcName, const QString &dbSchema, QStringList &handledServices, const CodeGen::ExerciseDependency &ed)
{
// TODO: make product-dependent
QString dbUser;
if (dbSchema.startsWith(QLatin1String("ex_")))
dbUser = QLatin1String("ets_ex_00");
else
dbUser = QLatin1String("ets_") + dbSchema;
return executeSql(svcName, dbSchema, dbUser, handledServices, ed);
}
/**
* @brief Execute all sql-stmts for a service
* @param svcName
* @param dbSchema
* @param dbUser
* @return
*/
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::executeSql(const QString &svcName, const QString &dbSchema, const QString &dbUser, QStringList &handledServices, const CodeGen::ExerciseDependency &ed)
{
CodeGen::SqlGenerator *sqlGen = m_sqlGeneratorMap.value(svcName);
if (!sqlGen)
return false;
// check if this service was already handled
if (handledServices.contains(svcName))
{
INFO_LOG(m_logger) << "Service already in handledServices: " << svcName;
return true;
}
handledServices += svcName;
INFO_LOG(m_logger) << "Added service to handledServices: " << svcName;
//check if schema is empty at beginning, after executing some statements is not empty anymore
// for exercise dependent services we execute all statements
bool schemaEmpty = schemaIsEmpty(dbSchema) || ed == CodeGen::ExerciseDependency::Exercise;
// first execute all depedent services
const auto deps = sqlGen->getReferencedServices();
for (const QString &depSvc : deps)
{
if (!executeSql(depSvc, dbSchema, dbUser, handledServices, ed))
{
ERROR_LOG(m_logger) << "Failed to execute sql " << depSvc;
return false;
}
}
const QStringList stmts = sqlGen->getAllSqlStmts(dbSchema, ed);
NSqlQuery q(m_database);
int stmtExecuted = 0;
for (const QString &stmt : stmts)
{
if (stmt.isEmpty() || stmt.startsWith(QLatin1String("--")) || stmt.startsWith(QLatin1String("\n--")))
continue;
if ((stmt.startsWith(QLatin1String("DROP TABLE")) || stmt.startsWith(QLatin1String("INSERT INTO"))) && !schemaEmpty)
continue;
if ((stmt.startsWith(QLatin1String("DROP SEQUENCE")) || stmt.startsWith(QLatin1String("CREATE SEQUENCE"))) && !schemaEmpty)
continue;
if (!q.exec(stmt))
{
ERROR_LOG(m_logger) << "Error while executing stmt: '" << stmt << "' : " << q.getLastSqlError();
return false;
}
checkAliveTimer();
NOTICE_LOG(m_logger) << "Statement executed: " << stmt;
++stmtExecuted;
}
DEBUG_LOG(m_logger) << "#" << stmtExecuted << " stmts executed.";
return true;
}
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::schemaIsEmpty(const QString &dbSchema) const
{
NSqlQuery q(m_database);
QString stmt = QLatin1String("SELECT COUNT(*) FROM information_schema.tables") +
QLatin1String(" WHERE table_schema = '") + dbSchema + QLatin1String("'");
if (!q.exec(stmt))
{
INFO_LOG(m_logger) << "Schema does not exist : " << dbSchema;
return false;
}
q.next();
if (q.value(0).toInt() > 0)
return false;
return true;
}
/**
* @brief Execute only sql stmts for tables & table-related items (Sequences/PK/FK/Indexes)
*
* Note: sql stmts have an 'CREATE <> IF NOT EXISTS'
* Additional check for tableExists necessary for inserting data
* @param svcName
* @param dbSchema
* @param dbUser
* @return
*/
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::executeSqlTableRelated(const QString &svcName, const QString &dbSchema, const QString &dbUser)
{
if (!m_sqlGeneratorMap.contains(svcName))
return false;
const CodeGen::SqlGenerator *sqlGen = m_sqlGeneratorMap.value(svcName);
const CodeGen::SqlGenerator::SqlStatements sqlStatements = sqlGen->getStatements(CodeGen::ExerciseDependency::Global);
const CodeGen::SqlGenerator::SqlItems sqlItems = sqlGen->getSqlItems(CodeGen::ExerciseDependency::Global);
// 1. Step: Check which tables will be created
QStringList tablesNewlyCreated;
for (const QString &tableName : sqlItems.tables)
{
if (!existsTable(tableName, dbSchema))
tablesNewlyCreated += tableName;
}
// 2. Step: Set db-environment (privileges/search path)
INFO_LOG(m_logger) << "Create TABLES for dbSchema = " << dbSchema << ", dbUser = " << dbUser;
QStringList sqlStmts = sqlGen->getDbSettings(dbSchema);
// 3. Step: execute stmts
// Note: create sequence if not exists in postgresql 9.5
sqlStmts += sqlStatements.createTables;
if (!executeStmts(sqlStmts))
return false;
INFO_LOG(m_logger) << " #" << tablesNewlyCreated.size() << " tables created. ";
// Primary Keys
if (!executsSqlConstraints(sqlItems.primaryKeys, sqlStatements.createPrimaryKeys, dbSchema))
return false;
// TODO: Foreign Keys
//if (!executsSqlConstraints(sqlGen->getForeignKeys(), sqlStatements.createForeignKeys, dbSchema))
// return false;
// Indexes
if (!executsSqlConstraints(sqlItems.constraints, sqlStatements.createConstraints, dbSchema))
return false;
// 4. Step: Handle sequences separately
sqlStmts = sqlStatements.createSequences;
int listIndexStmts = 0;
for (QString sequenceName : sqlItems.sequences)
{
// TODO: remove when using postgresql 9.5 feature create sequence if not exists
sequenceName.remove(dbSchema + QLatin1Char('.'));
QString stmt = QLatin1String("SELECT exists_sequence('") + sequenceName + QLatin1String("', '")
+ dbSchema + QLatin1String("')");
NSqlQuery q(m_database);
if (!q.execAndFetch(stmt))
return false;
// Sequence exists?
if (!q.value(0).toBool())
q.exec(sqlStmts.at(listIndexStmts));
listIndexStmts++;
}
bool ok = true;
for (const QString &table : qAsConst(tablesNewlyCreated))
{
if (!insertTableData(table, dbSchema, sqlGen))
{
ERROR_LOG(m_logger) << "Failed to insert data for table " << dbSchema << "." << table;
ok &= false;
}
}
return ok;
}
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::executsSqlConstraints(const QStringList &constraintList, const QStringList &sqlStmts,
const QString &dbSchema)
{
int listIndexStmts = 0;
int createdConstraints = 0;
for (const QString &constraintName : constraintList)
{
DEBUG_LOG(m_logger) << "Check constraint '" << constraintName << "'";
// TODO: remove when using postgresql 9.6 feature create constraint if not exists
QString stmt = QLatin1String("SELECT exists_constraint('") + constraintName.toLower() + QLatin1String("', '")
+ dbSchema + QLatin1String("', '") + QLatin1String("i')");
//DEBUG_LOG(m_logger) << "stmt = " << stmt;
NSqlQuery q(m_database);
if (!q.execAndFetch(stmt))
return false;
// Constraint exists?
if (!q.value(0).toBool())
{
DEBUG_LOG(m_logger) << "Constraint '" << constraintName << "' does not exist, creating ..";
if (!q.exec(sqlStmts.at(listIndexStmts)))
return false;
createdConstraints++;
}
listIndexStmts++;
}
INFO_LOG(m_logger) << " #" << createdConstraints << " constraints created. ";
return true;
}
void ExerciseTableGenerator::checkAliveTimer()
{
if (m_emitAliveTimer.elapsed() > 2000) // longer than 2 sec -> send heartbeat
{
emit aliveSignal();
m_emitAliveTimer.start();
}
}
// (Re)Create sql statements for given exercise
// --------------------------------------------------------------------------------------------------------
void ExerciseTableGenerator::generateSqlDataForExercise(int exerciseId)
{
for (CodeGen::SqlGenerator *sqlGen : qAsConst(m_sqlGeneratorMap))
{
// recreate sql statement with correct schema name (e.g. for serials)
if (!sqlGen->generateData(exerciseId))
ERROR_LOG(m_logger) << "Error generating sql for exercise " << exerciseId;
}
}
/**
* @brief ExerciseTableGenerator::executeStmts
* @param stmts
* @return true on success
*/
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::executeStmts(const QStringList &stmts)
{
NSqlQuery q(m_database);
int stmtExecuted = 0;
for (const QString &stmt : stmts)
{
if (stmt.isEmpty() || stmt.startsWith(QLatin1String("--")) || stmt.startsWith(QLatin1String("\n--")))
continue;
if (!q.exec(stmt))
{
ERROR_LOG(m_logger) << "Error while executing stmt: '" << stmt << "' : " << q.getLastSqlError();
return false;
}
DEBUG_LOG(m_logger) << "Executed statement: " << stmt;
++stmtExecuted;
}
DEBUG_LOG(m_logger) << "#" << stmtExecuted << " stmts executed.";
return true;
}
/**
* @brief ExerciseTableGenerator::insertTableData
* insert possible static data for newly created table
* @param table
* @param dbSchema
* @return true on success
*/
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::insertTableData(const QString &table, const QString &dbSchema, const CodeGen::SqlGenerator *sqlGen)
{
// inserts are only enumValues
const QStringList &enumInserts = sqlGen->getEnumerationInsertValues(table, dbSchema);
if (!executeStmts(enumInserts))
return false;
INFO_LOG(m_logger) << " #" << enumInserts.count() << " rows inserted for " << dbSchema << "." << table;
// TODO: add other static data
return true;
}
// --------------------------------------------------------------------------------------------------------
// Write sql-stmts to a file for a specific exercise
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::writeExerciseServicesSql(qint32 exerciseId)
{
bool ok = false;
QString schemaName = DBCommon::getExerciseSchemaName(exerciseId);
for (auto it = m_sqlGeneratorMap.constBegin(); it != m_sqlGeneratorMap.constEnd(); ++it)
{
if (!it.value()->isServiceExerciseIndependent())
ok &= writeServiceSqlToFile(it.key(), schemaName);
}
return ok;
}
/**
* @brief getAvailableExerciseServices
* @return
*/
// --------------------------------------------------------------------------------------------------------
QStringList ExerciseTableGenerator::getAvailableExerciseServices() const
{
QStringList services;
for (auto it = m_sqlGeneratorMap.constBegin(); it != m_sqlGeneratorMap.constEnd(); ++it)
{
if (!it.value()->isServiceExerciseIndependent())
services.append(it.key());
}
return services;
}
// --------------------------------------------------------------------------------------------------------
QStringList ExerciseTableGenerator::getAvailableGlobalServices() const
{
QStringList services;
for (auto it = m_sqlGeneratorMap.constBegin(); it != m_sqlGeneratorMap.constEnd(); ++it)
{
// if (it.value()->isServiceExerciseIndependent() || it.value()->getExerciseDependency() == CodeGen::ExerciseDependency::GlobalAndExercise)
if (it.value()->isServiceGlobal())
{
services.append(it.key());
DEBUG_LOG(m_logger) << "Global service found: " << it.key();
}
}
return services;
}
// Execute sql-stmts for a specific exercise (for all exercise-depended services)
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::executeSql(qint32 exerciseId)
{
const QStringList services = getAvailableExerciseServices();
// Define schema for given exerciseId
QString schemaName = DBCommon::getExerciseSchemaName(exerciseId);
DEBUG_LOG(m_logger) << "Create schema for EXERCISE " << schemaName;
generateSqlDataForExercise(exerciseId);
m_emitAliveTimer.start();
// Create dbSchema & tables, .. in a transaction
// => can be reverted if an error occured
DBTransaction trans(m_database);
QStringList handledServices;
for (const QString &svcName : services)
{
CodeGen::SqlGenerator *sqlGen = m_sqlGeneratorMap.value(svcName);
if (!sqlGen)
{
ERROR_LOG(m_logger) << "Can not find sqlgen for service " << svcName
<< " - Skipping!";
continue;
}
DEBUG_LOG(m_logger) << "-----------------------------------------------------------------------------";
DEBUG_LOG(m_logger) << "Create exercise dependent tables/views/indexes/.. for service '" << svcName << "', exercise: " << exerciseId;
if (!executeSql(svcName, schemaName, handledServices, CodeGen::ExerciseDependency::Exercise))
{
ERROR_LOG(m_logger) << "Failed to create db-environment (tables/views/indexes/..) for service '" << svcName << "'";
trans.rollback();
return false;
}
checkAliveTimer();
QFile predefinedSql;
QString filePath = m_xmlPath;
filePath.replace(QLatin1String("xml"), QLatin1String("sql"));
DEBUG_LOG(m_logger) << "File Path: " << filePath;
predefinedSql.setFileName(filePath + svcName + QLatin1String("_predefined_data.sql"));
DEBUG_LOG(m_logger) << "Predefined filename " << predefinedSql.fileName();
if (predefinedSql.exists())
{
if (!predefinedSql.open(QIODevice::ReadOnly | QIODevice::Text))
return false;
QStringList predefinedInserts;
QTextStream textStream(&predefinedSql);
while (!textStream.atEnd())
{
QString line = textStream.readLine();
line = line.replace(QStringLiteral("%ex_id%"), QString::number(exerciseId));
DEBUG_LOG(m_logger) << "Line: " << line;
predefinedInserts << line;
}
predefinedSql.close();
DEBUG_LOG(m_logger) << "Executing predefined statements: \n" << predefinedInserts.join(QStringLiteral("\n"));
if (!executeStmts(predefinedInserts))
return false;
checkAliveTimer();
}
INFO_LOG(m_logger) << "db-environment (tables/views/indexes/..) created for service '" << svcName << "'";
}
// Commit all dbChanges
trans.commit();
resetSearchPath();
return true;
}
// Execute sql-stmts for all exercise-independent services
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::executeSql()
{
const QStringList services = getAvailableGlobalServices();
DEBUG_LOG(m_logger) << "Found global services :" << services.size() ;
// Create dbSchema & tables, .. in a transaction
// => can be reverted if an error occured
DBTransaction trans(m_database);
QStringList handledServices;
NSqlQuery querySchemaExists(m_database);
// querySchemaExists.prepare(QStringLiteral("SELECT count(*) FROM information_schema.schemata where schema_name = :sn"));
querySchemaExists.prepare(QStringLiteral("SELECT count(*) FROM information_schema.tables where table_schema = :sn"));
for (const QString &svc : services)
{
// we should not recreate already existing schemas since this will destroy FKs from ex_N.foo.bar_id to <global>.bar.id
querySchemaExists.bindValue(QStringLiteral(":sn"), svc);
if (querySchemaExists.execAndFetch())
{
const int num = querySchemaExists.value(0).toInt();
if (num > 0)
{
DEBUG_LOG(m_logger) << "Schema " << svc << " already exists - skip creating tables for this service";
continue;
}
}
DEBUG_LOG(m_logger) << "-------------------------------------------------------------------------------";
DEBUG_LOG(m_logger) << "Create exercise independent tables/views/indexes/.. for service '" << svc << "'";
if (!executeSql(svc, svc, handledServices, CodeGen::ExerciseDependency::Global))
{
ERROR_LOG(m_logger) << "Failed to create db-environment (tables/views/indexes/..) for service '" << svc << "'";
trans.rollback();
return false;
}
INFO_LOG(m_logger) << "db-environment (tables/views/indexes/..) created for service '" << svc << "'";
}
// Commit all dbChanges
trans.commit();
resetSearchPath();
return true;
}
// Convenience functions
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::createExerciseSchema(qint32 exerciseId)
{
// Define schema for given exerciseId
QString dbSchema = DBCommon::getExerciseSchemaName(exerciseId);
QString dbUser = QLatin1String("ets_ex_00");
return createDbSchemaIfNotExists(dbSchema, dbUser);
}
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::dropExerciseSchema(qint32 exerciseId, bool bDropUser)
{
// Define schema for given exerciseId
QString dbSchema = DBCommon::getExerciseSchemaName(exerciseId);
// TODO: make product-depended
return dropDbSchema(dbSchema, bDropUser ? QLatin1String("ets_") + dbSchema : QString());
}
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::resetSearchPath() const
{
// Reset of SEARCH_PATH (has changed in the process of creating a db)
NSqlQuery q(m_database);
QString stmt = QLatin1String("SET SEARCH_PATH TO ") + m_ownSchemaName + QLatin1String(", PUBLIC");
return q.exec(stmt);
}
// --------------------------------------------------------------------------------------------------------
bool ExerciseTableGenerator::isServiceGlobal(const QString &svcAbbr) const
{
CodeGen::SqlGenerator *sqlGen = m_sqlGeneratorMap.value(svcAbbr);
if (!sqlGen)
return false;
return sqlGen->isServiceGlobal();
}
} // namespace DBAdministration