Initial commit
This commit is contained in:
commit
b7d90b9afe
16245 files changed
+3396666
No files matched your search
+791
@@ -0,0 +1,791 @@
|
||||
/**
|
||||
* 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
|
||||
Reference in new issue
Block a user