- training_area_telegrams.cpp: Fixed QJsonObject initializer syntax (Qt6 requires {{}} not ({}))
- training_area_telegrams.cpp: Fixed fromJson() methods to access m_* members directly
- training_area_telegrams.cpp: Fixed brace mismatches in multiple toJson() methods
- training_area_telegrams.h: Changed Header struct to public members for consistency
- tx_mixin.cpp: Fixed getEquipmentNumber() template for direct member access
- lts_coordinate.cpp: Added missing #include <iomanip>
- MIGRATION_STATUS.md: Added migration progress tracking document
C++17 constraint enforced due to RTI Connext DDS 7.7.0 incompatibility with C++20+
Build status: BUILD_RTI=OFF successful (0 errors, warnings only)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
893 lines
34 KiB
C++
893 lines
34 KiB
C++
|
|
|
|
#include "SqlGenerator.h"
|
|
|
|
#include "StructBuilder.h"
|
|
#include "XmlReader.h"
|
|
|
|
#include <NGLLogger/NGLLoggerBootstrap.h>
|
|
#include <QtCore/QStringList>
|
|
#include <QtCore/QFile>
|
|
#include <QtCore/QTextStream>
|
|
|
|
using namespace CodeGen;
|
|
|
|
static QString getExerciseSchemaName(int exerciseId)
|
|
{
|
|
return QStringLiteral("ex_") + QString::number(exerciseId);
|
|
}
|
|
|
|
SqlGenerator::SqlGenerator(XmlReader* xmlReader, StructBuilder* structBuilder)
|
|
: m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.SqlGenerator")),
|
|
m_xmlReader(xmlReader), m_structBuilder(structBuilder), m_createdByXmlFile(false), m_isValid(true),
|
|
m_exerciseId(0)
|
|
{
|
|
}
|
|
|
|
SqlGenerator::SqlGenerator(const QString& xmlFile)
|
|
: m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.SqlGenerator")),
|
|
m_xmlReader(new XmlReader), m_structBuilder(new StructBuilder(m_xmlReader)), m_createdByXmlFile(true),
|
|
m_isValid(false), m_exerciseId(0)
|
|
{
|
|
m_isValid = m_xmlReader->readServiceFile(xmlFile, QString()) && m_structBuilder->buildStructures();
|
|
}
|
|
|
|
SqlGenerator::~SqlGenerator()
|
|
{
|
|
if (m_createdByXmlFile)
|
|
{
|
|
delete m_xmlReader;
|
|
delete m_structBuilder;
|
|
}
|
|
}
|
|
|
|
bool SqlGenerator::generateData(int exerciseId)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Generating SQL data for service " << getServiceAbbr() << " in exercise " << exerciseId
|
|
<< ", this: " << this;
|
|
|
|
m_exerciseId = exerciseId;
|
|
m_sqlGlobal = SqlStatements();
|
|
m_sqlExercise = SqlStatements();
|
|
m_sqlItemsGlobal = SqlItems();
|
|
m_sqlItemsExercise = SqlItems();
|
|
m_referencedSevices.clear();
|
|
|
|
if (!handleTables())
|
|
return false;
|
|
|
|
if (!handleViews())
|
|
return false;
|
|
|
|
if (!handleEnumerations())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SqlGenerator::createStatementsForTable(const QString& tableName, const QString& tableNameOut)
|
|
{
|
|
const QMap<QString, CompleteTableInformation> tables = m_structBuilder->getDbTables();
|
|
for (const CompleteTableInformation& cti : tables)
|
|
{
|
|
if (cti.name != tableName)
|
|
continue;
|
|
CompleteTableInformation ctiOut = cti;
|
|
ctiOut.name = tableNameOut;
|
|
return handleTable(ctiOut);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SqlGenerator::handleTables()
|
|
{
|
|
const QMap<QString, CompleteTableInformation> tables = m_structBuilder->getDbTables();
|
|
for (const CompleteTableInformation& cti : tables)
|
|
{
|
|
if (!handleTable(cti))
|
|
return false;
|
|
}
|
|
|
|
const QMap<QString, CompleteTableInformation> recTables = m_structBuilder->getRecordingTopicTables();
|
|
MemberInformation miColumnIsValid;
|
|
miColumnIsValid.db.columnName = QStringLiteral("dds_is_alive");
|
|
miColumnIsValid.db.datatype = QStringLiteral("boolean");
|
|
miColumnIsValid.db.defaultValue = QStringLiteral("true");
|
|
MemberInformation miInsertionTime;
|
|
miInsertionTime.cpp.type = QStringLiteral("timestamp");
|
|
miInsertionTime.db.columnName = QStringLiteral("insert_time");
|
|
miInsertionTime.db.defaultValue = QStringLiteral("now()");
|
|
miInsertionTime.db.usedForInsert = false;
|
|
m_structBuilder->fixupQVariantConversions(miInsertionTime);
|
|
ConstraintInformation ci;
|
|
ci.name = QStringLiteral("IDX_sample_time");
|
|
ci.affectedColumns = QStringList({QStringLiteral("sample_time")});
|
|
ci.type = ConstraintInformation::Type::index;
|
|
for (const CompleteTableInformation& cti : recTables)
|
|
{
|
|
CompleteTableInformation ctiTmp(cti);
|
|
ctiTmp.columns.push_back(miColumnIsValid);
|
|
ctiTmp.columns.push_back(miInsertionTime);
|
|
ctiTmp.constraints.push_back(ci);
|
|
if (!handleTable(ctiTmp))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SqlGenerator::handleViews()
|
|
{
|
|
const QMap<QString, ViewInformation> views = m_structBuilder->getDbViews();
|
|
for (const ViewInformation& vi : views)
|
|
{
|
|
if (!handleView(vi))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SqlGenerator::handleView(const ViewInformation& vi)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Handle view " << vi.name;
|
|
|
|
const QString parent = vi.baseTable;
|
|
|
|
DEBUG_LOG(m_logger) << "Parent = " << parent;
|
|
|
|
if (!parent.isEmpty())
|
|
{
|
|
const CompleteStructInformation csi = m_structBuilder->structInformationByTableName(parent);
|
|
const QString createViewStmt(QStringLiteral("CREATE OR REPLACE VIEW %1 AS SELECT %2\nFROM %3\nWHERE %4;\n"));
|
|
const QString dropViewStmt(QStringLiteral("DROP VIEW IF EXISTS %1;"));
|
|
|
|
DEBUG_LOG(m_logger) << "csi.primaryTable = " << csi.primaryTable.data()->name;
|
|
|
|
if (!csi.primaryTable.isNull())
|
|
{
|
|
QStringList fromTables;
|
|
fromTables += vi.baseSchema + QLatin1Char('.') + vi.baseTable;
|
|
|
|
QStringList whereConditions;
|
|
if (vi.filter.contains(QLatin1String("exercise_id")))
|
|
whereConditions += QLatin1String("exercise_id=") + QString::number(m_exerciseId);
|
|
|
|
QStringList fqColumnNames;
|
|
|
|
for (const MemberInformation& mi : csi.member)
|
|
{
|
|
if (mi.db.usedForView)
|
|
fqColumnNames += mi.db.table + QLatin1Char('.') + mi.db.columnName + QLatin1String(" AS ") +
|
|
mi.db.getColumnNameView();
|
|
}
|
|
|
|
DEBUG_LOG(m_logger) << "fqColumnNames = " << fqColumnNames.join(QStringLiteral(", "));
|
|
|
|
if (!whereConditions.isEmpty())
|
|
addToCreateViews(createViewStmt.arg(csi.viewName, fqColumnNames.join(QStringLiteral(", ")),
|
|
fromTables.join(QStringLiteral(", ")),
|
|
whereConditions.join(QStringLiteral(" AND\n"))),
|
|
vi.exerciseDependency);
|
|
addToDropViews(dropViewStmt.arg(csi.viewName), vi.exerciseDependency);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SqlGenerator::handleTable(const CompleteTableInformation& cti)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Handle table " << cti.name;
|
|
|
|
if (cti.columns.isEmpty())
|
|
{
|
|
WARNING_LOG(m_logger) << "Table " << cti.name
|
|
<< " has no DatabaseServiceStruct - not generating anything for this!";
|
|
return true;
|
|
}
|
|
|
|
const QString dropTableStmt(QStringLiteral("DROP TABLE IF EXISTS %1 CASCADE;"));
|
|
const QString createTableStmt(QStringLiteral("CREATE TABLE IF NOT EXISTS %1 (%2\n);"));
|
|
const QString commentTableStmt(QStringLiteral("COMMENT ON TABLE %1 IS '%2';"));
|
|
|
|
QStringList tableComments;
|
|
addToDropTables(dropTableStmt.arg(cti.name), cti.exerciseDependency);
|
|
|
|
if ((cti.exerciseDependency & Global) == Global)
|
|
{
|
|
addToCreateTables(createTableStmt.arg(
|
|
cti.name, getTableColumnCreateStatements(cti.name, cti.columns, &tableComments, Global)),
|
|
Global);
|
|
}
|
|
if ((cti.exerciseDependency & Exercise) == Exercise)
|
|
{
|
|
addToCreateTables(createTableStmt.arg(cti.name, getTableColumnCreateStatements(cti.name, cti.columns,
|
|
&tableComments, Exercise)),
|
|
Exercise);
|
|
}
|
|
addToTables(cti.name, cti.exerciseDependency);
|
|
|
|
if (!cti.comment.isEmpty())
|
|
addToCreateTables(commentTableStmt.arg(cti.name, fixupComment(cti.comment)), cti.exerciseDependency);
|
|
addToCreateTables(tableComments, cti.exerciseDependency);
|
|
|
|
const QString parent = m_xmlReader->getStruct2BaseClassRelations().value(cti.name);
|
|
const CompleteStructInformation csi = m_structBuilder->structInformationByTableName(cti.name);
|
|
if (!parent.isEmpty())
|
|
{
|
|
const QString createViewStmt(QStringLiteral("CREATE OR REPLACE VIEW %1 AS SELECT %2\nFROM %3\nWHERE %4;\n"));
|
|
const QString dropViewStmt(QStringLiteral("DROP VIEW IF EXISTS %1;"));
|
|
if (!csi.primaryTable.isNull())
|
|
{
|
|
QStringList fqColumnNames;
|
|
QStringList fromTables;
|
|
QStringList whereConditions;
|
|
fromTables += cti.name;
|
|
|
|
for (const MemberInformation& mi : csi.member)
|
|
{
|
|
if (mi.db.usedForView)
|
|
fqColumnNames += mi.db.table + QLatin1Char('.') + mi.db.columnName + QLatin1String(" AS ") +
|
|
mi.db.getColumnNameView();
|
|
if (mi.cpp.isReadOnly && mi.db.isForeignKey)
|
|
{
|
|
|
|
for (const QSharedPointer<TableInformation>& tiPtr : csi.tables)
|
|
{
|
|
if (tiPtr->name == mi.db.table)
|
|
{
|
|
const QSharedPointer<ForeignKeyInformation> fki =
|
|
tiPtr->fkInformationForColumn(mi.db.table, mi.db.columnName);
|
|
if (!fki.isNull())
|
|
{
|
|
|
|
if (!fromTables.contains(fki->destination.tableName))
|
|
{
|
|
whereConditions += fki->destination.tableName + QLatin1Char('.') +
|
|
fki->destination.columnName + QLatin1String(" = ") +
|
|
fki->source.tableName + QLatin1Char('.') +
|
|
fki->source.columnName;
|
|
fromTables += fki->destination.tableName;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!whereConditions.isEmpty())
|
|
{
|
|
addToCreateViews(createViewStmt.arg(csi.viewName, fqColumnNames.join(QStringLiteral(", ")),
|
|
fromTables.join(QStringLiteral(", ")),
|
|
whereConditions.join(QStringLiteral(" AND\n"))),
|
|
cti.exerciseDependency);
|
|
}
|
|
addToDropViews(dropViewStmt.arg(csi.viewName), cti.exerciseDependency);
|
|
}
|
|
}
|
|
|
|
if (!generateDbNotifications(cti.name, csi.dbNotifications, cti.exerciseDependency))
|
|
return false;
|
|
|
|
generateForeignKeys(cti.foreignKeys, cti.exerciseDependency);
|
|
|
|
generateConstraints(cti.name, cti.constraints, cti.exerciseDependency);
|
|
|
|
return true;
|
|
}
|
|
|
|
QString SqlGenerator::getTableColumnCreateStatements(const QString& tableName,
|
|
const QVector<MemberInformation>& columns,
|
|
QStringList* tableComments, ExerciseDependency ed)
|
|
{
|
|
Q_ASSERT(ed == Global || ed == Exercise);
|
|
|
|
const QString commentColumnStmt(QStringLiteral("COMMENT ON COLUMN %1 IS '%2';"));
|
|
|
|
int maxLength = 1;
|
|
for (const MemberInformation& mi : columns)
|
|
maxLength = std::max(maxLength, static_cast<int>(mi.db.columnName.length()));
|
|
maxLength += 1;
|
|
|
|
QStringList retCols;
|
|
retCols.reserve(columns.size() + 1);
|
|
retCols += QString();
|
|
for (int i = 0; i < columns.size(); ++i)
|
|
{
|
|
const MemberInformation& mi = columns.at(i);
|
|
const FullQualifiedColumnInformation& ci = mi.db;
|
|
const QString spaces(maxLength - ci.columnName.length(), QLatin1Char(' '));
|
|
QString column = ci.columnName + spaces + ci.datatype;
|
|
if (ci.hasSequence)
|
|
{
|
|
|
|
const QString seqDescr = generateSequence(tableName, ci, ed);
|
|
QString schema;
|
|
if (ed == Global)
|
|
schema = m_xmlReader->getServiceAbbr();
|
|
else
|
|
schema = getExerciseSchemaName(m_exerciseId);
|
|
|
|
column += QLatin1String(" DEFAULT nextval(( '") + schema + QLatin1Char('.') + seqDescr +
|
|
QLatin1String("'::text)::regclass)");
|
|
}
|
|
if (!ci.canBeNull)
|
|
column += QLatin1String(" NOT NULL");
|
|
if (!ci.hasSequence && !ci.defaultValue.isEmpty())
|
|
column += QLatin1String(" DEFAULT ") + ci.defaultValue;
|
|
if (i < columns.size() - 1)
|
|
column += QLatin1Char(',');
|
|
if (!ci.comment.isEmpty())
|
|
{
|
|
const QString comment(fixupComment(ci.comment));
|
|
const QString fqn = ci.table + QLatin1Char('.') + ci.columnName;
|
|
*tableComments += commentColumnStmt.arg(fqn, comment);
|
|
column += QLatin1String(" -- ") + comment;
|
|
}
|
|
|
|
retCols += column;
|
|
|
|
if (ci.isPrimaryKey)
|
|
generatePrimaryKey(tableName, ci, ed);
|
|
}
|
|
return retCols.join(QStringLiteral("\n "));
|
|
}
|
|
|
|
QString SqlGenerator::generateSequence(const QString& tableName, const FullQualifiedColumnInformation& ci,
|
|
ExerciseDependency ed)
|
|
{
|
|
const QString sequenceName = tableName + QLatin1Char('_') + ci.columnName + QLatin1String("_seq");
|
|
|
|
const QString createSequenceStmt(
|
|
QLatin1String("CREATE SEQUENCE IF NOT EXISTS %1 INCREMENT %2 START %3 MINVALUE %4;"));
|
|
|
|
const int minValue = ci.sequenceStart < 1 ? ci.sequenceStart : 1;
|
|
|
|
addToCreateSequences(
|
|
createSequenceStmt.arg(sequenceName).arg(ci.sequenceIncrement).arg(ci.sequenceStart).arg(minValue), ed);
|
|
addToSequences(sequenceName, ed);
|
|
|
|
return sequenceName;
|
|
}
|
|
|
|
void SqlGenerator::generatePrimaryKey(const QString& tableName, const FullQualifiedColumnInformation& ci,
|
|
ExerciseDependency ed)
|
|
{
|
|
QString pkName = QLatin1String("PK_") + tableName + QLatin1Char('_') + ci.columnName;
|
|
|
|
if (pkName.size() > 63)
|
|
pkName.resize(63);
|
|
|
|
addToPks(pkName, ed);
|
|
|
|
const QString pkConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 ADD CONSTRAINT %2 PRIMARY KEY (%3);"));
|
|
addToCreatePks(pkConstraint.arg(tableName, pkName, ci.columnName), ed);
|
|
|
|
const QString pkDropConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 DROP CONSTRAINT IF EXISTS %2 CASCADE;"));
|
|
addToDropConstraints(pkDropConstraint.arg(tableName, pkName), ed);
|
|
}
|
|
|
|
void SqlGenerator::generateForeignKeys(const QVector<QSharedPointer<ForeignKeyInformation>>& foreignKeys,
|
|
ExerciseDependency ed)
|
|
{
|
|
for (const QSharedPointer<ForeignKeyInformation>& fk : foreignKeys)
|
|
generateForeignKey(fk, ed);
|
|
}
|
|
|
|
void SqlGenerator::generateForeignKey(const QSharedPointer<ForeignKeyInformation>& foreignKey, ExerciseDependency ed)
|
|
{
|
|
QString fkName = QLatin1String("FK_") + foreignKey->source.tableName + QLatin1Char('_') +
|
|
foreignKey->source.columnName + QLatin1Char('_') + foreignKey->destination.tableName +
|
|
QLatin1Char('_') + foreignKey->destination.columnName;
|
|
|
|
if (fkName.size() > 63)
|
|
fkName = foreignKey->name;
|
|
|
|
addToFks(fkName, ed);
|
|
|
|
QString fkConstraint(QStringLiteral("ALTER TABLE %1 ADD CONSTRAINT %2 FOREIGN KEY (%3) REFERENCES %4 (%5)"));
|
|
switch (foreignKey->deleteAction)
|
|
{
|
|
case ForeignKeyInformation::OnDeleteAction::NoAction:
|
|
fkConstraint += QStringLiteral(" ON DELETE NO ACTION");
|
|
break;
|
|
case ForeignKeyInformation::OnDeleteAction::Cascade:
|
|
fkConstraint += QStringLiteral(" ON DELETE CASCADE ON UPDATE CASCADE");
|
|
break;
|
|
case ForeignKeyInformation::OnDeleteAction::Restrict:
|
|
fkConstraint += QStringLiteral(" ON DELETE RESTRICT");
|
|
break;
|
|
case ForeignKeyInformation::OnDeleteAction::SetNull:
|
|
fkConstraint += QStringLiteral(" ON DELETE SET NULL");
|
|
break;
|
|
case ForeignKeyInformation::OnDeleteAction::SetDefault:
|
|
fkConstraint += QStringLiteral(" ON DELETE SET DEFAULT");
|
|
break;
|
|
}
|
|
|
|
fkConstraint += QLatin1Char(';');
|
|
|
|
QString destination;
|
|
if (!foreignKey->destination.schema.isEmpty())
|
|
{
|
|
const QString& extSchema = foreignKey->destination.schema;
|
|
destination = extSchema + QLatin1Char('.') + foreignKey->destination.tableName;
|
|
}
|
|
else
|
|
destination = foreignKey->destination.tableName;
|
|
|
|
const QString& extSvcAbbr = foreignKey->destination.serviceAbbr;
|
|
|
|
if (!extSvcAbbr.isEmpty() && !m_referencedSevices.contains(extSvcAbbr))
|
|
m_referencedSevices += extSvcAbbr;
|
|
|
|
addToCreateFks(fkConstraint.arg(foreignKey->source.tableName, fkName, foreignKey->source.columnName, destination,
|
|
foreignKey->destination.columnName),
|
|
ed);
|
|
|
|
const QString fkDropConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 DROP CONSTRAINT IF EXISTS %2 CASCADE;"));
|
|
addToDropConstraints(fkDropConstraint.arg(foreignKey->source.tableName, fkName), ed);
|
|
}
|
|
|
|
void SqlGenerator::generateConstraints(const QString& tableName, const QVector<ConstraintInformation>& constraints,
|
|
ExerciseDependency ed)
|
|
{
|
|
for (const ConstraintInformation& constraint : constraints)
|
|
generateConstraint(tableName, constraint, ed);
|
|
}
|
|
|
|
void SqlGenerator::generateConstraint(const QString& tableName, const ConstraintInformation& constraint,
|
|
ExerciseDependency ed)
|
|
{
|
|
const QString dropConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 DROP CONSTRAINT IF EXISTS %2 CASCADE;"));
|
|
switch (constraint.type)
|
|
{
|
|
case ConstraintInformation::Type::index: {
|
|
QString constraintName = QLatin1String("IDX_") + tableName + QLatin1Char('_') +
|
|
constraint.affectedColumns.join(QStringLiteral("_"));
|
|
|
|
if (constraintName.size() > 63)
|
|
constraintName.resize(63);
|
|
|
|
const QString index(QStringLiteral("CREATE INDEX %1 ON %2 (%3);"));
|
|
|
|
addToCreateConstraints(
|
|
index.arg(constraintName, tableName, constraint.affectedColumns.join(QStringLiteral(", "))), ed);
|
|
addToConstraints(constraintName, ed);
|
|
|
|
addToDropConstraints(dropConstraint.arg(tableName, constraintName), ed);
|
|
|
|
break;
|
|
}
|
|
case ConstraintInformation::Type::unique: {
|
|
QString constraintName = QLatin1String("UQ_") + tableName + QLatin1Char('_') +
|
|
constraint.affectedColumns.join(QStringLiteral("_"));
|
|
|
|
if (constraintName.size() > 63)
|
|
constraintName.resize(63);
|
|
const QString unique(QStringLiteral("ALTER TABLE %1 ADD CONSTRAINT %2 UNIQUE (%3);"));
|
|
|
|
addToCreateConstraints(
|
|
unique.arg(tableName, constraintName, constraint.affectedColumns.join(QStringLiteral(", "))), ed);
|
|
addToConstraints(constraintName, ed);
|
|
|
|
addToDropConstraints(dropConstraint.arg(tableName, constraintName), ed);
|
|
|
|
break;
|
|
}
|
|
case ConstraintInformation::Type::unknown:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool SqlGenerator::handleEnumerations()
|
|
{
|
|
const QVector<EnumInformation> enumerations = m_xmlReader->getEnumerations();
|
|
for (const EnumInformation& ei : enumerations)
|
|
{
|
|
if (!handleEnumeration(ei))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SqlGenerator::handleEnumeration(const EnumInformation& ei)
|
|
{
|
|
const QStringList sql = handleEnumeration(ei, QString());
|
|
if (m_sqlItemsGlobal.tables.contains(ei.tableName))
|
|
m_sqlGlobal.enumerationValues += sql;
|
|
if (m_sqlItemsExercise.tables.contains(ei.tableName))
|
|
m_sqlExercise.enumerationValues += sql;
|
|
|
|
return true;
|
|
}
|
|
|
|
QStringList SqlGenerator::handleEnumeration(const EnumInformation& ei, const QString& dbSchema) const
|
|
{
|
|
DEBUG_LOG(m_logger) << "Handle enumeration " << ei.enumName;
|
|
|
|
if (ei.tableName.isEmpty())
|
|
{
|
|
INFO_LOG(m_logger) << "No table for enumeration " << ei.enumName << " - not creating inserts for this enum";
|
|
return QStringList();
|
|
}
|
|
|
|
const QString insertEnum(QStringLiteral("INSERT INTO %1 (%2) VALUES (%3);"));
|
|
const QString columnNames(QStringLiteral("id, name"));
|
|
const QString tablename = dbSchema.isEmpty() ? ei.tableName : dbSchema + QLatin1Char('.') + ei.tableName;
|
|
|
|
QStringList enumerationValues;
|
|
for (const EnumKeyValuePair& evp : ei.values)
|
|
{
|
|
const QString values(QString::number(evp.value) + QLatin1String(", '") + fixupComment(evp.key) +
|
|
QLatin1Char('\''));
|
|
enumerationValues += insertEnum.arg(tablename, columnNames, values);
|
|
}
|
|
|
|
return enumerationValues;
|
|
}
|
|
|
|
bool SqlGenerator::generateDbNotifications(const QString& tableName,
|
|
const QVector<DbNotifcationInformation>& notifications,
|
|
ExerciseDependency ed)
|
|
{
|
|
for (const DbNotifcationInformation& notification : notifications)
|
|
{
|
|
if (notification.paramNames.isEmpty() || notification.paramNames.size() > 1)
|
|
{
|
|
ERROR_LOG(m_logger) << "Can not handle more than one parameter";
|
|
continue;
|
|
}
|
|
generateDbNotification(notification, ed);
|
|
if (!generateDbNotificationTrigger(tableName, notification, ed))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void SqlGenerator::generateDbNotification(const DbNotifcationInformation& notification, ExerciseDependency ed)
|
|
{
|
|
const QString func(QStringLiteral("CREATE OR REPLACE FUNCTION public.%1()\n"
|
|
"RETURNS TRIGGER AS $$\n"
|
|
"DECLARE\n"
|
|
" toNotify RECORD;\n"
|
|
" toReturn RECORD;\n"
|
|
"BEGIN\n"
|
|
" IF (TG_OP = 'INSERT') THEN\n"
|
|
" toNotify = NEW;\n"
|
|
" toReturn = NEW;\n"
|
|
" ELSIF (TG_OP = 'UPDATE') THEN\n"
|
|
" toNotify = OLD;\n"
|
|
" toReturn = NEW;\n"
|
|
" ELSIF (TG_OP = 'DELETE') THEN\n"
|
|
" toNotify = OLD;\n"
|
|
" toReturn = OLD;\n"
|
|
" END IF;\n"
|
|
" \n"
|
|
" PERFORM pg_notify('%2', CAST(toNotify.%3 AS text));\n"
|
|
" RETURN toReturn;\n"
|
|
"END;\n"
|
|
"$$ LANGUAGE plpgsql;"));
|
|
|
|
const QString triggerFuncName(getNotificationFunctionName(notification));
|
|
addToDbFunctions(func.arg(triggerFuncName, notification.methodName, notification.paramNames.first()), ed);
|
|
}
|
|
|
|
bool SqlGenerator::generateDbNotificationTrigger(const QString& tableName, const DbNotifcationInformation& notification,
|
|
ExerciseDependency ed)
|
|
{
|
|
|
|
const QString drop(QStringLiteral("DROP TRIGGER IF EXISTS %1 ON %2;"));
|
|
const QString func(QStringLiteral("CREATE TRIGGER %1\n") +
|
|
QStringLiteral("%2 ON %3 FOR EACH ROW EXECUTE PROCEDURE public.%4();"));
|
|
|
|
for (DbNotifcationInformation::NotificationType notifyType : notification.notificationType)
|
|
{
|
|
QString when;
|
|
QString triggerNameShortcut;
|
|
switch (notifyType)
|
|
{
|
|
case DbNotifcationInformation::NotificationType::BeforeInsert:
|
|
when = QStringLiteral("BEFORE INSERT");
|
|
triggerNameShortcut = QStringLiteral("bi");
|
|
break;
|
|
case DbNotifcationInformation::NotificationType::AfterInsert:
|
|
when = QStringLiteral("AFTER INSERT");
|
|
triggerNameShortcut = QStringLiteral("ai");
|
|
break;
|
|
case DbNotifcationInformation::NotificationType::BeforeUpdate:
|
|
when = QStringLiteral("BEFORE UPDATE");
|
|
triggerNameShortcut = QStringLiteral("bu");
|
|
break;
|
|
case DbNotifcationInformation::NotificationType::AfterUpdate:
|
|
when = QStringLiteral("AFTER UPDATE");
|
|
triggerNameShortcut = QStringLiteral("au");
|
|
break;
|
|
case DbNotifcationInformation::NotificationType::BeforeDelete:
|
|
when = QStringLiteral("BEFORE DELETE");
|
|
triggerNameShortcut = QStringLiteral("bd");
|
|
break;
|
|
case DbNotifcationInformation::NotificationType::AfterDelete:
|
|
when = QStringLiteral("AFTER DELETE");
|
|
triggerNameShortcut = QStringLiteral("ad");
|
|
break;
|
|
}
|
|
const QString triggerName(QLatin1String("trg_") + triggerNameShortcut + QLatin1Char('_') +
|
|
notification.methodName);
|
|
const QString triggerFuncName(getNotificationFunctionName(notification));
|
|
addToDbFunctionTrigger(drop.arg(triggerName, tableName), ed);
|
|
addToDbFunctionTrigger(func.arg(triggerName, when, tableName, triggerFuncName), ed);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
QString SqlGenerator::getNotificationFunctionName(const DbNotifcationInformation& notification) const
|
|
{
|
|
return getServiceAbbr() + QLatin1Char('_') + notification.methodName;
|
|
}
|
|
|
|
QString SqlGenerator::fixupComment(const QString& comment)
|
|
{
|
|
|
|
QString ret = comment;
|
|
ret = ret.remove(QLatin1Char('\''));
|
|
ret = ret.replace(QLatin1Char(';'), QLatin1Char(','));
|
|
return ret;
|
|
}
|
|
|
|
QStringList SqlGenerator::getDbSettings(const QString& dbSchema) const
|
|
{
|
|
QStringList data;
|
|
|
|
QString dbUser(QStringLiteral("ets_"));
|
|
if (m_exerciseId > 0)
|
|
dbUser += QStringLiteral("ex_00");
|
|
else
|
|
dbUser += m_xmlReader->getServiceAbbr().toLower();
|
|
|
|
data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') +
|
|
QLatin1String(" GRANT SELECT,INSERT,UPDATE,DELETE,TRUNCATE ON TABLES TO ") + dbUser +
|
|
QLatin1Char(';'));
|
|
|
|
data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') +
|
|
QLatin1String(" GRANT SELECT, REFERENCES ON TABLES TO ets_ro;"));
|
|
|
|
data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') +
|
|
QLatin1String(" GRANT EXECUTE ON FUNCTIONS TO ") + dbUser + QLatin1Char(';'));
|
|
|
|
data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') +
|
|
QLatin1String(" GRANT ALL ON SEQUENCES TO ") + dbUser + QLatin1Char(';'));
|
|
|
|
data.append(QLatin1String("SET SEARCH_PATH TO ") + dbSchema + QLatin1String(", PUBLIC;"));
|
|
|
|
QStringList deps;
|
|
deps.reserve(m_referencedSevices.size());
|
|
for (const QString& svc : m_referencedSevices)
|
|
deps += QStringLiteral("-- DEPENDS: ") + svc;
|
|
|
|
return deps + data;
|
|
}
|
|
|
|
QStringList SqlGenerator::getAllSqlStmts(const QString& dbSchema, ExerciseDependency ed) const
|
|
{
|
|
DEBUG_LOG(m_logger) << "Creating statements for service " << getServiceAbbr() << " in exercise " << m_exerciseId
|
|
<< ", this: " << this;
|
|
|
|
QStringList data;
|
|
|
|
const SqlStatements& sql = getStatements(ed);
|
|
if (sql.isEmpty())
|
|
return data;
|
|
|
|
if (!dbSchema.isEmpty())
|
|
data << getDbSettings(dbSchema);
|
|
|
|
data.append(QStringLiteral("\n-- Drop tables/views/sequences/constraints"));
|
|
data.append(sql.dropViews);
|
|
data.append(sql.dropConstraints);
|
|
data.append(sql.dropTables);
|
|
data.append(sql.dropSequences);
|
|
|
|
data.append(QStringLiteral("\n-- Create tables"));
|
|
data.append(sql.createTables);
|
|
|
|
data.append(QStringLiteral("\n-- Create sequence(s)"));
|
|
data.append(sql.createSequences);
|
|
|
|
data.append(QStringLiteral("\n-- Create primary keys"));
|
|
data.append(sql.createPrimaryKeys);
|
|
|
|
data.append(QStringLiteral("\n-- Create foreign keys"));
|
|
data.append(sql.createForeignKeys);
|
|
|
|
data.append(QStringLiteral("\n-- Create views"));
|
|
data.append(sql.createViews);
|
|
|
|
data.append(
|
|
QStringLiteral("\n-- --------------------------------------------------------------------------------"));
|
|
data.append(QStringLiteral("\n-- Inserts for static data i.e. enums\n"));
|
|
data.append(sql.enumerationValues);
|
|
|
|
data.append(
|
|
QStringLiteral("\n-- --------------------------------------------------------------------------------"));
|
|
data.append(QStringLiteral("\n-- Notify functions for callbacks (signals in db interface)\n"));
|
|
data.append(sql.dbFunctions);
|
|
|
|
data.append(
|
|
QStringLiteral("\n-- --------------------------------------------------------------------------------"));
|
|
data.append(QStringLiteral("\n-- Notify function trigger for callbacks (signals in db interface)\n"));
|
|
data.append(sql.dbFunctionTrigger);
|
|
|
|
data.append(
|
|
QStringLiteral("\n-- --------------------------------------------------------------------------------"));
|
|
data.append(QStringLiteral("\n-- Indexes \n"));
|
|
data.append(sql.createConstraints);
|
|
|
|
return data;
|
|
}
|
|
|
|
QStringList SqlGenerator::getEnumerationInsertValues(const QString& table, const QString& dbSchema) const
|
|
{
|
|
const QVector<EnumInformation> enumerations = m_xmlReader->getEnumerations();
|
|
|
|
for (const EnumInformation& ei : enumerations)
|
|
{
|
|
if (ei.tableName == table)
|
|
return handleEnumeration(ei, dbSchema);
|
|
}
|
|
return QStringList();
|
|
}
|
|
|
|
bool SqlGenerator::writeSqlToFile(const QString& dbSchema, const QString& sqlFile, ExerciseDependency ed) const
|
|
{
|
|
return writeFile(sqlFile, getAllSqlStmts(dbSchema, ed).join(QLatin1Char('\n')));
|
|
}
|
|
|
|
bool SqlGenerator::writeSql(const QString& filePath, ExerciseDependency ed)
|
|
{
|
|
Q_ASSERT(ed == Global || ed == Exercise);
|
|
|
|
QString sqlFile = filePath + getServiceAbbr();
|
|
if (ed == ExerciseDependency::Exercise)
|
|
sqlFile += QLatin1Char('_') + getExerciseSchemaName(m_exerciseId);
|
|
sqlFile += QLatin1String(".sql");
|
|
|
|
const SqlStatements& sql = getStatements(ed);
|
|
if (sql.isEmpty())
|
|
{
|
|
DEBUG_LOG(m_logger) << "Sql file is emtpy: " << sqlFile;
|
|
return writeFile(sqlFile, QString());
|
|
}
|
|
|
|
const QString dbSchema =
|
|
(ed == ExerciseDependency::Exercise ? getExerciseSchemaName(m_exerciseId) : getServiceAbbr());
|
|
|
|
INFO_LOG(m_logger) << "Creating sql-stmt for schema (" << dbSchema << ")";
|
|
return writeSqlToFile(dbSchema, sqlFile, ed);
|
|
}
|
|
|
|
template <typename T>
|
|
static void addInternal(QStringList& global, QStringList& exercise, const T& stmt, ExerciseDependency ed)
|
|
{
|
|
switch (ed)
|
|
{
|
|
case ExerciseDependency::Global:
|
|
global += stmt;
|
|
break;
|
|
case ExerciseDependency::Exercise:
|
|
exercise += stmt;
|
|
break;
|
|
case ExerciseDependency::GlobalAndExercise:
|
|
global += stmt;
|
|
exercise += stmt;
|
|
break;
|
|
case ExerciseDependency::Undefined:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SqlGenerator::addToDropSequences(const QString& stmt, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlGlobal.dropSequences, m_sqlExercise.dropSequences, stmt, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToDropTables(const QString& stmt, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlGlobal.dropTables, m_sqlExercise.dropTables, stmt, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToDropViews(const QString& stmt, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlGlobal.dropViews, m_sqlExercise.dropViews, stmt, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToCreateConstraints(const QString& stmt, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlGlobal.createConstraints, m_sqlExercise.createConstraints, stmt, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToDropConstraints(const QString& stmt, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlGlobal.dropConstraints, m_sqlExercise.dropConstraints, stmt, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToCreateFks(const QString& stmt, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlGlobal.createForeignKeys, m_sqlExercise.createForeignKeys, stmt, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToCreatePks(const QString& stmt, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlGlobal.createPrimaryKeys, m_sqlExercise.createPrimaryKeys, stmt, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToCreateSequences(const QString& stmt, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlGlobal.createSequences, m_sqlExercise.createSequences, stmt, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToCreateTables(const QString& stmt, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlGlobal.createTables, m_sqlExercise.createTables, stmt, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToCreateTables(const QStringList& stmts, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlGlobal.createTables, m_sqlExercise.createTables, stmts, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToCreateViews(const QString& stmt, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlGlobal.createViews, m_sqlExercise.createViews, stmt, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToDbFunctions(const QString& stmt, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlGlobal.dbFunctions, m_sqlExercise.dbFunctions, stmt, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToDbFunctionTrigger(const QString& stmt, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlGlobal.dbFunctionTrigger, m_sqlExercise.dbFunctionTrigger, stmt, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToConstraints(const QString& name, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlItemsGlobal.constraints, m_sqlItemsExercise.constraints, name, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToFks(const QString& name, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlItemsGlobal.foreignKeys, m_sqlItemsExercise.foreignKeys, name, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToPks(const QString& name, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlItemsGlobal.primaryKeys, m_sqlItemsExercise.primaryKeys, name, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToSequences(const QString& name, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlItemsGlobal.sequences, m_sqlItemsExercise.sequences, name, ed);
|
|
}
|
|
|
|
void SqlGenerator::addToTables(const QString& name, ExerciseDependency ed)
|
|
{
|
|
addInternal(m_sqlItemsGlobal.tables, m_sqlItemsExercise.tables, name, ed);
|
|
}
|
|
|
|
bool SqlGenerator::writeFile(const QString& fn, const QString& content) const
|
|
{
|
|
QFile f(fn);
|
|
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
|
{
|
|
ERROR_LOG(m_logger) << "Error opening file '" << f.fileName() << "' for writing: " << f.errorString();
|
|
return false;
|
|
}
|
|
f.write(content.toUtf8());
|
|
if (!content.isEmpty())
|
|
f.write("\n");
|
|
f.close();
|
|
return true;
|
|
}
|