944 lines
32 KiB
C++
Executable File
944 lines
32 KiB
C++
Executable File
|
|
|
|
|
|
#include "SqlGenerator.h"
|
|
#include "StructBuilder.h"
|
|
#include "XmlReader.h"
|
|
|
|
#include <QtCore/QFile>
|
|
#include <QtCore/QTextStream>
|
|
#include <QStringList>
|
|
|
|
#include <NGLLogger/NGLLoggerBootstrap.h>
|
|
|
|
using namespace CodeGen;
|
|
|
|
|
|
static QString getExerciseSchemaName(int exerciseId)
|
|
{
|
|
return QStringLiteral("ex_") + QString::number(exerciseId);
|
|
}
|
|
|
|
SqlGenerator::SqlGenerator(XmlReader *xmlReader, StructBuilder *structBuilder)
|
|
: m_xmlReader(xmlReader)
|
|
, m_structBuilder(structBuilder)
|
|
, m_createdByXmlFile(false)
|
|
, m_isValid(true)
|
|
, m_exerciseId(0)
|
|
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.SqlGenerator"))
|
|
{}
|
|
|
|
SqlGenerator::SqlGenerator(const QString &xmlFile)
|
|
: m_xmlReader(new XmlReader)
|
|
, m_structBuilder(new StructBuilder(m_xmlReader))
|
|
, m_createdByXmlFile(true)
|
|
, m_isValid(false)
|
|
, m_exerciseId(0)
|
|
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.SqlGenerator"))
|
|
{
|
|
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, 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> ¬ifications,
|
|
ExerciseDependency ed)
|
|
{
|
|
for (const DbNotifcationInformation ¬ification : 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 ¬ification,
|
|
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 ¬ification,
|
|
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 ¬ification) 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;
|
|
}
|