#include "SqlFormattingUtils.h" #include #include #include "catalog/PgKeywordList.h" #include "catalog/PgConstraint.h" #include "catalog/PgAttributeContainer.h" #include "catalog/PgClass.h" #include "catalog/PgClassContainer.h" #include "catalog/PgIndex.h" #include "catalog/PgNamespace.h" #include "catalog/PgNamespaceContainer.h" #include "catalog/PgDatabaseCatalog.h" namespace { QString escapeInternal(const QString &input, QChar quote_char) { int num_quotes = 0; /* single or double, depending on as_ident */ const int len = input.length(); for (int idx = 0; idx < len; ++idx) if (input[idx] == quote_char) ++num_quotes; int output_size = len + num_quotes + 2; // + 2 for the quotes QString output; output.reserve(output_size); output += quote_char; if (num_quotes == 0) { output += input; } else { for (int idx = 0; idx < len; ++idx) { QChar c = input[idx]; output += c; if (c == quote_char) output += c; } } output += quote_char; return output; } } QString escapeIdent(const QString &input) { return escapeInternal(input, '"'); } QString escapeLiteral(const QString &input) { return escapeInternal(input, '\''); } bool identNeedsQuotes(QString ident) { if (ident[0].isDigit()) return true; for (auto c : ident) if ((c < 'a' || c > 'z') && c != '_' && (c < '0' || c > '9')) return true; auto kw = getPgsqlKeyword(ident); if (kw == nullptr) return false; if (kw->getCategory() == UNRESERVED_KEYWORD) return false; return true; } QString quoteIdent(QString ident) { assert(ident.length() > 0); static const QChar dquote = u'"'; if (identNeedsQuotes(ident)) { QString out; out += dquote; out += ident.replace("\"", "\"\""); out += dquote; return out; } return ident; } QString dollarQuoteString(const QString &value) { QString def_tag = "BODY"; QString tag = QString("$%1$").arg(def_tag); int counter = 1; while (value.indexOf(tag) >= 0) tag = QString("$%1%2$").arg(def_tag, counter++); return tag + value + tag; } //QString genSchemaPrefix(const PgNamespace &ns) //{ // QString str; // if (!ns.name.isEmpty()) { // str = quoteIdent(ns.name) % QString::fromUtf16(u"."); // } // return str; //} //QString genFQTableName(const PgDatabaseCatalog &catalog, const PgClass &cls) //{ // auto ns = catalog.namespaces()->getByKey(cls.relnamespace); ////cls.fullyQualifiedQuotedObjectName() // return ns->quotedObjectName() % "." % cls.quotedObjectName(); //} QString genAlterTable(const PgDatabaseCatalog &, const PgClass &cls) { return "ALTER TABLE " % cls.fullyQualifiedQuotedObjectName(); // genFQTableName(catalog, cls); } QString getDropConstraintDefinition(const PgDatabaseCatalog &catalog, const PgConstraint &constraint) { const PgClass *cls = catalog.classes()->getByKey(constraint.relid); return genAlterTable(catalog, *cls) % " DROP CONSTRAINT " % quoteIdent(constraint.objectName()) % ";"; } /// Helper class that builds comma seperated identifier list class IdentListString { public: void add(QString ident) { if (!m_str.isEmpty()) m_str += ","; m_str += quoteIdent(ident); } const QString& str() const { return m_str; } private: QString m_str; }; QString getColumnNameList(const PgDatabaseCatalog &catalog, Oid relid, const SmallAttNumVec<5> &attnums) { IdentListString result; const auto ac = catalog.attributes(); for (auto an : attnums) { result.add(ac->getByKey({ relid, an })->name); } return result.str(); } QString getForeignKeyConstraintDefinition(const PgDatabaseCatalog &catalog, const PgConstraint &constraint, const QString &indent) { //PgClass cls = catalog.classes()->getByKey(constraint.relid); const PgClass *fcls = catalog.classes()->getByKey(constraint.frelid); QString deferrable; QString validated; if (!constraint.validated) validated += " NOT VALID"; if (constraint.deferrable) { deferrable = QLatin1String(" DEFERRABLE INITIALLY ") % (constraint.deferred ? "DEFERRED" : "IMMEDIATE"); } return "\n" + indent + "FOREIGN KEY (" % getColumnNameList(catalog, constraint.relid, constraint.key) % ")\n" + indent + "REFERENCES " % fcls->fullyQualifiedQuotedObjectName() % " (" % getColumnNameList(catalog, constraint.frelid, constraint.fkey) % ")\n" + indent + "MATCH " % ForeignKeyMatchToString(constraint.fmatchtype) % " ON UPDATE " % ForeignKeyActionToString(constraint.fupdtype) % " ON DELETE " % ForeignKeyActionToString(constraint.fdeltype) % deferrable % validated; } QString getForeignKeyConstraintReferences(const PgDatabaseCatalog &catalog, const PgConstraint &constraint) { const PgClass *fcls = catalog.classes()->getByKey(constraint.frelid); QString deferrable; QString validated; if (!constraint.validated) validated += " NOT VALID"; if (constraint.deferrable) { deferrable = QLatin1String(" DEFERRABLE INITIALLY ") % (constraint.deferred ? "DEFERRED" : "IMMEDIATE"); } return "REFERENCES " % fcls->fullyQualifiedQuotedObjectName() % " (" % getColumnNameList(catalog, constraint.frelid, constraint.fkey) % ") MATCH " % ForeignKeyMatchToString(constraint.fmatchtype) % " ON UPDATE " % ForeignKeyActionToString(constraint.fupdtype) % " ON DELETE " % ForeignKeyActionToString(constraint.fdeltype) % deferrable % validated; } QString getForeignKeyConstraintReferencesShort(const PgDatabaseCatalog &catalog, const PgConstraint &constraint) { const PgClass *fcls = catalog.classes()->getByKey(constraint.frelid); QString deferrable; QString validated; if (!constraint.validated) validated += " NOT VALID"; if (constraint.deferrable) { deferrable = QLatin1String(" DEFERRABLE") % (constraint.deferred ? " INITIALLY DEFERRED" : ""); } QString on_update = constraint.fupdtype == ForeignKeyAction::NoAction ? QString() : " ON UPDATE " % ForeignKeyActionToString(constraint.fupdtype); QString on_delete = constraint.fdeltype == ForeignKeyAction::NoAction ? QString() : " ON DELETE " % ForeignKeyActionToString(constraint.fdeltype); QString match_type = constraint.fmatchtype == ForeignKeyMatch::Simple ? QString() : " MATCH " % ForeignKeyMatchToString(constraint.fmatchtype); return fcls->fullyQualifiedQuotedObjectName() % " (" % getColumnNameList(catalog, constraint.frelid, constraint.fkey) % ")" % match_type % on_update % on_delete % deferrable % validated; } QString getPrimaryKeyConstraintDefinition(const PgDatabaseCatalog &catalog, const PgConstraint &constraint) { QString ddl = " PRIMARY KEY (" % getColumnNameList(catalog, constraint.relid, constraint.key) % ")"; return ddl; } QString getUniqueConstraintDefinition(const PgDatabaseCatalog &catalog, const PgConstraint &constraint) { QString ddl = " UNIQUE (" % getColumnNameList(catalog, constraint.relid, constraint.key) % ")"; return ddl; } QString getAlterTableConstraintDefinition(const PgDatabaseCatalog &catalog, const PgConstraint &constraint) { const PgClass *cls = catalog.classes()->getByKey(constraint.relid); QString result = genAlterTable(catalog, *cls) % "\n ADD "; result += getConstraintDefinition(catalog, constraint, " ") % ";"; return result; } QString getConstraintDefinition(const PgDatabaseCatalog &catalog, const PgConstraint &constraint, const QString &indent) { QString result = "CONSTRAINT " % quoteIdent(constraint.objectName()); switch (constraint.type) { case ConstraintType::ForeignKey: result += getForeignKeyConstraintDefinition(catalog, constraint, indent); break; case ConstraintType::PrimaryKey: result += getPrimaryKeyConstraintDefinition(catalog, constraint); break; case ConstraintType::Unique: result += getUniqueConstraintDefinition(catalog, constraint); break; case ConstraintType::Check: default: result = result % " " % constraint.definition; break; } return result; }