Files
qt5-legatus-legacy/Lts/Common/CodeGen/SqlGenerator.cpp
T
2026-08-13 18:33:10 +02:00

949 lines
34 KiB
C++
Executable File

/**
* @RDE_HEADER@
* Generate SQL statements from Xml service files
* $Id$
* $HeadURL$
* $Author$
*/
#include "SqlGenerator.h"
#include "StructBuilder.h"
#include "XmlReader.h"
#include <QtCore/QFile>
#include <QtCore/QTextStream>
#include <QStringList>
#include <NGLLogger/NGLLoggerBootstrap.h>
using namespace CodeGen;
// to avoid dependency to ngldb
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;
// fqColumnNames += QLatin1String("*");
// buildup view create statement
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;
// buildup view create statement
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)
{
// readOnly -> FK
// get FK Informations
// get correct TableInformation
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())
{
// IO 26.05.2016: ignore second foreign key on same tables, the create view sql is more complicated in this case (see ppm_sensor_view)
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;
}
// must not be called with ExerciseDependency::Undefined or Exercisedependency::GlobalAndExercise !!
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';"));
// only for beautification of the output
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)
{
// TODO: dbSchema needs added to sequence
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");
// "IF NOT EXISTS" for sequences possible now from 9.5 on
// http://www.postgresql.org/docs/9.5/static/sql-createsequence.html
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;
// truncate long names to 63
if (pkName.size() > 63) pkName.resize(63);
addToPks(pkName, ed);
// ADD IF NOT EXISTS => Postgresql 9.6
// Using stored procedure 'exists_constraint' for now (14.03.16) (HR)
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;
// truncate long names to 64
//if (fkName.size() > 63) fkName.resize(63);
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: // no deletion possible at all
fkConstraint += QStringLiteral(" ON DELETE NO ACTION");
break;
case ForeignKeyInformation::OnDeleteAction::Cascade:
fkConstraint += QStringLiteral(" ON DELETE CASCADE ON UPDATE CASCADE");
break;
case ForeignKeyInformation::OnDeleteAction::Restrict: // no deletion until childs are deleted
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(';');
// the work if the fk points to a global or local schema is already done in the EA export script (ExportExternalFk())
// if foreignKey->destination.schema is set, then we've to use it ... :)
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;
// depends on another service .. remember depedency
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("_"));
// truncate long names to 63
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("_"));
// truncate long names to 63
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;
// Festlegung: id - name = key - value
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)
{
// CREATE TRIGGER uebung_1ins_tln_zuordn BEFORE INSERT ON tln_zuordnung FOR EACH ROW EXECUTE PROCEDURE public.ins_zuordnung();
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)
{
// fixup chars
QString ret = comment;
ret = ret.remove(QLatin1Char('\''));
ret = ret.replace(QLatin1Char(';'), QLatin1Char(','));
return ret;
}
QStringList SqlGenerator::getDbSettings(const QString &dbSchema) const
{
QStringList data;
// see svc_db_impl.cpp.grantlee - DatabaseInterfaceImpl::dbUser()
QString dbUser(QStringLiteral("ets_"));
if (m_exerciseId > 0)
dbUser += QStringLiteral("ex_00"); //getExerciseSchemaName(m_exerciseId);
else
dbUser += m_xmlReader->getServiceAbbr().toLower();
// Grant table privileges
data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') +
QLatin1String(" GRANT SELECT,INSERT,UPDATE,DELETE,TRUNCATE ON TABLES TO ") + dbUser + QLatin1Char(';'));
// Grant select to all users in <product>_ro
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(';'));
// Set search path
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);
// Write drop-stmts
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);
// Write create-stmts (tables)
data.append(QStringLiteral("\n-- Create tables"));
data.append(sql.createTables);
// Create sequence(s)
data.append(QStringLiteral("\n-- Create sequence(s)"));
data.append(sql.createSequences);
// Write primary key(s)
data.append(QStringLiteral("\n-- Create primary keys"));
data.append(sql.createPrimaryKeys);
// Write foreign key(s)
data.append(QStringLiteral("\n-- Create foreign keys"));
data.append(sql.createForeignKeys);
// Write create-stmts (views)
data.append(QStringLiteral("\n-- Create views"));
data.append(sql.createViews);
// Write inserts for enums
data.append(QStringLiteral("\n-- --------------------------------------------------------------------------------"));
data.append(QStringLiteral("\n-- Inserts for static data i.e. enums\n"));
data.append(sql.enumerationValues);
// Write notify functions
data.append(QStringLiteral("\n-- --------------------------------------------------------------------------------"));
data.append(QStringLiteral("\n-- Notify functions for callbacks (signals in db interface)\n"));
data.append(sql.dbFunctions);
// Write notify functions
data.append(QStringLiteral("\n-- --------------------------------------------------------------------------------"));
data.append(QStringLiteral("\n-- Notify function trigger for callbacks (signals in db interface)\n"));
data.append(sql.dbFunctionTrigger);
// Write constraints
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);
// ExerciseDependency == Global -> write exercise independent sql statements -> <svc>.sql
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());
}
// exerciseId > 0 -> write exercise dependent sql statements -> <svc>_ex_<exerciseId>.sql
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;
}