pgLab/pglablib/SqlFormattingUtils.cpp
eelke 742fd0a4d3 SelectionEditorFactory + ItemModel + ItemModelFactory combination is working
in new EditTableWidget

(EditTableWidget is very much WIP)
2018-12-15 20:27:40 +01:00

433 lines
12 KiB
C++

#include "SqlFormattingUtils.h"
#include <QStringBuilder>
#include <cassert>
#include "PgKeywordList.h"
#include "PgConstraint.h"
#include "PgAttributeContainer.h"
#include "PgClass.h"
#include "PgClassContainer.h"
#include "PgIndex.h"
#include "PgNamespace.h"
#include "PgNamespaceContainer.h"
#include "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;
}
//rolename=xxxx -- privileges granted to a role
// =xxxx -- privileges granted to PUBLIC
// r -- SELECT ("read")
// w -- UPDATE ("write")
// a -- INSERT ("append")
// d -- DELETE
// D -- TRUNCATE
// x -- REFERENCES
// t -- TRIGGER
// X -- EXECUTE
// U -- USAGE
// C -- CREATE
// c -- CONNECT
// T -- TEMPORARY
// arwdDxt -- ALL PRIVILEGES (for tables, varies for other objects)
// * -- grant option for preceding privilege
// /yyyy -- role that granted this privilege