#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, bool as_ident) { int num_quotes = 0; /* single or double, depending on as_ident */ int num_backslashes = 0; QChar quote_char = as_ident ? '"' : '\''; // Doorloop input // tel quotes // tel backslashes const int len = input.length(); for (int idx = 0; idx < len; ++idx) { QChar c = input[idx]; if (c == quote_char) ++num_quotes; else if (c == '\\') ++num_backslashes; } int output_size = len + num_quotes + 2; // + 2 for the quotes if (!as_ident && num_backslashes > 0) output_size += num_backslashes + 2; // +2 so whe can add the " E" QString output; output.reserve(output_size); if (!as_ident && num_backslashes > 0) output += " E"; output += quote_char; if (num_quotes == 0 && (num_backslashes == 0 || as_ident)) { output += input; } else { for (int idx = 0; idx < len; ++idx) { QChar c = input[idx]; output += c; if (c == quote_char || (!as_ident && c == '\\')) output += c; } } output += quote_char; return output; } } QString escapeIdent(const QString &input) { return escapeInternal(input, true); } QString escapeLiteral(const QString &input) { return escapeInternal(input, false); } //char * //PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident) //{ // const char *s; // char *result; // char *rp; // int num_quotes = 0; /* single or double, depending on as_ident */ // int num_backslashes = 0; // int input_len; // int result_size; // char quote_char = as_ident ? '"' : '\''; //// /* We must have a connection, else fail immediately. */ //// if (!conn) //// return NULL; // /* Scan the string for characters that must be escaped. */ // for (s = str; (s - str) < len && *s != '\0'; ++s) // { // if (*s == quote_char) // ++num_quotes; // else if (*s == '\\') // ++num_backslashes; // else if (IS_HIGHBIT_SET(*s)) // { // int charlen; // /* Slow path for possible multibyte characters */ // charlen = pg_encoding_mblen(conn->client_encoding, s); // /* Multibyte character overruns allowable length. */ // if ((s - str) + charlen > len || memchr(s, 0, charlen) != NULL) // { // printfPQExpBuffer(&conn->errorMessage, // libpq_gettext("incomplete multibyte character\n")); // return NULL; // } // /* Adjust s, bearing in mind that for loop will increment it. */ // s += charlen - 1; // } // } // /* Allocate output buffer. */ // input_len = s - str; // result_size = input_len + num_quotes + 3; /* two quotes, plus a NUL */ // if (!as_ident && num_backslashes > 0) // result_size += num_backslashes + 2; // result = rp = (char *) malloc(result_size); // if (rp == NULL) // { // printfPQExpBuffer(&conn->errorMessage, // libpq_gettext("out of memory\n")); // return NULL; // } // /* // * If we are escaping a literal that contains backslashes, we use the // * escape string syntax so that the result is correct under either value // * of standard_conforming_strings. We also emit a leading space in this // * case, to guard against the possibility that the result might be // * interpolated immediately following an identifier. // */ // if (!as_ident && num_backslashes > 0) // { // *rp++ = ' '; // *rp++ = 'E';1 // } // /* Opening quote. */ // *rp++ = quote_char; // /* // * Use fast path if possible. // * // * We've already verified that the input string is well-formed in the // * current encoding. If it contains no quotes and, in the case of // * literal-escaping, no backslashes, then we can just copy it directly to // * the output buffer, adding the necessary quotes. // * // * If not, we must rescan the input and process each character // * individually. // */ // if (num_quotes == 0 && (num_backslashes == 0 || as_ident)) // { // memcpy(rp, str, input_len); // rp += input_len; // } // else // { // for (s = str; s - str < input_len; ++s) // { // if (*s == quote_char || (!as_ident && *s == '\\')) // { // *rp++ = *s; // *rp++ = *s; // } // else if (!IS_HIGHBIT_SET(*s)) // *rp++ = *s; // else // { // int i = pg_encoding_mblen(conn->client_encoding, s); // while (1) // { // *rp++ = *s; // if (--i == 0) // break; // ++s; /* for loop will provide the final increment */ // } // } // } // } // /* Closing quote and terminating NUL. */ // *rp++ = quote_char; // *rp = '\0'; // return result; //} 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; else if (kw->getCategory() == UNRESERVED_KEYWORD) return false; // if (forTypes && sk->category == COL_NAME_KEYWORD) // return false; return true; } QString quoteIdent(QString ident) { assert(ident.length() > 0); static const wchar_t dquote = L'"'; if (identNeedsQuotes(ident)) { QString out; out += dquote; out += ident.replace("\"", "\"\""); out += dquote; return out; } else 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) { //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 FOREIGN KEY (" % getColumnNameList(catalog, constraint.relid, constraint.key) % ")\n REFERENCES " % fcls->fullyQualifiedQuotedObjectName() % " (" % getColumnNameList(catalog, constraint.frelid, constraint.fkey) % ")\n 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) { QString result = "CONSTRAINT " % quoteIdent(constraint.objectName()); switch (constraint.type) { case ConstraintType::ForeignKey: result += getForeignKeyConstraintDefinition(catalog, constraint); 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; }