pgLab/pglablib/catalog/PgProc.cpp
eelke c2c01cf431 Generic GRANT/REVOKE generation from ACL's complete.
Moved the owned concept to PgServerObject as it is needed for the generic
acl functionality that is also in PgServerObject.
2018-12-25 14:25:15 +01:00

393 lines
10 KiB
C++

#include "PgProc.h"
#include "std_utils.h"
#include "SqlFormattingUtils.h"
#include "PgDatabaseCatalog.h"
#include "PgLanguageContainer.h"
#include "PgTypeContainer.h"
#include <QStringBuilder>
#include <boost/assert.hpp>
namespace {
Arg::Mode CharToArgMode(char c)
{
switch (c) {
case 'i': return Arg::In;
case 'o': return Arg::Out;
case 'b': return Arg::InOut;
case 'v': return Arg::Variadic;
case 't': return Arg::Table;
}
throw std::runtime_error("Unexpected value for pg_proc.proargmodes");
}
QString ArgModeToString(Arg::Mode m)
{
switch (m) {
case Arg::In: return "IN";
case Arg::Out: return "OUT";
case Arg::InOut: return "INOUT";
case Arg::Variadic: return "VARIADIC";
case Arg::Table: return "TABLE";
}
throw std::runtime_error("Unexpected value for Arg::Mode");
}
std::vector<QString> getArrayFromCommaSeparatedList(const QString &str)
{
std::vector<QString> res;
const int len = str.length();
if (len == 0)
return res;
// setup start state for parsing
int index = 0, brackets = 0, start_array = 0;
bool single_quote = false, double_quote = false;
for(; index < len; index++) {
QChar ch = str[index];
if (!double_quote && ch == L'\'')
single_quote = !single_quote;
else if (!single_quote && ch == L'"')
double_quote = !double_quote;
else if (!double_quote && !single_quote && ch == L'(')
brackets++;
else if (!double_quote && !single_quote && ch == L')')
brackets--;
else if (!double_quote && !single_quote && brackets == 0 && ch == L',') {
if (index != start_array)
res.push_back(str.mid(start_array, index - 1).trimmed());
else
res.push_back(QString());
start_array = index + 1;
}
}
if (double_quote || single_quote || brackets != 0)
throw std::runtime_error("Error parsing comma seperated array");
// Add last value to array
res.push_back(str.right(start_array).trimmed());
return res;
}
}
void operator<<(ProcKind &s, const Pgsql::Value &v)
{
const char *c = v.c_str();
switch (*c) {
case 'f':
s = ProcKind::Function;
break;
case 'p':
s = ProcKind::Procedure;
break;
case 'a':
s = ProcKind::Aggregate;
break;
case 'w':
s = ProcKind::Window;
break;
default:
throw std::runtime_error("Unexpected value for ProcKind");
}
}
void operator<<(ParallelMode &s, const Pgsql::Value &v)
{
const char *c = v.c_str();
switch (*c) {
case 'u':
s = ParallelMode::Unsafe;
break;
case 'r':
s = ParallelMode::Restricted;
break;
case 's':
s = ParallelMode::Safe;
break;
default:
throw std::runtime_error("Unexpected value for ParallelMode");
}
}
QString parallelModeToSql(ParallelMode pm, bool explicit_unsafe)
{
switch (pm) {
case ParallelMode::Unsafe:
return explicit_unsafe ? "PARALLEL UNSAFE" : "";
case ParallelMode::Restricted:
return "PARALLEL RESTRICTED";
case ParallelMode::Safe:
return "PARALLEL SAFE";
default:
throw std::runtime_error("Unexpected value for pm in parallelModeToSql");
}
}
void PgProc::setArgs(
std::vector<Oid> argtypes,
std::vector<Oid> allargtypes,
std::vector<char> argmodes,
std::vector<QString> argnames,
std::optional<QString> argdefaults
)
{
// When all args are of type IN allargtypes is empty and only argtypes is filled
const std::vector<Oid> &types = allargtypes.empty() ? argtypes : allargtypes;
const size_t count = types.size();
m_args.clear();
m_args.reserve(count);
for (size_t index = 0; index < count; ++index) {
// we "forget" the default here because it is easier
// to apply them in reverse order when we have already
// filled the list
m_args.emplace_back(
types[index],
CharToArgMode(value_or(argmodes, index, 'i')),
value_or(argnames, index, QString()),
""
);
}
auto defaults_array = getArrayFromCommaSeparatedList(argdefaults.value_or(QString()));
// Apply defaults from end to start to IN en INOUT parameters (others can't have a default)
size_t arg_idx = m_args.size() - 1;
for (auto def_iter = defaults_array.rbegin(); def_iter != defaults_array.rend(); ++def_iter) {
// skip arguments with wrong mode
while (m_args[arg_idx].mode != Arg::In && m_args[arg_idx].mode != Arg::InOut)
if (arg_idx == 0) throw std::runtime_error("Error processing defaults");
else --arg_idx;
m_args[arg_idx].def = *def_iter;
}
}
const std::vector<Arg>& PgProc::args() const
{
return m_args;
}
QString PgProc::argListWithNames(bool multiline) const
{
QString args;
size_t nArgs = 0;
auto&& types = catalog().types();
for (auto&& arg_elem : m_args) {
// All Table arguments lies at the end of the list
// Do not include them as the part of the argument list
if (arg_elem.mode == Arg::Table)
break;
nArgs++;
if (args.length() > 0)
args += (multiline) ? ",\n " : ", ";
QString arg;
if (arg_elem.mode != Arg::In)
arg += ArgModeToString(arg_elem.mode);
if (!arg_elem.name.isEmpty()) {
if (!arg.isEmpty())
arg += " ";
arg += quoteIdent(arg_elem.name);
}
if (!arg.isEmpty())
arg += " ";
arg += types->getByKey(arg_elem.type)->objectName();
if (!arg_elem.def.isEmpty())
arg += " DEFAULT " + arg_elem.def;
args += arg;
}
if (multiline && nArgs > 1)
args = "\n " + args;
return args;
}
// Return the signature arguments list. If forScript = true, we format the list
// appropriately for use in a SELECT script.
QString PgProc::argSigList(const bool forScript) const
{
QString args;
auto&& types = catalog().types();
for (auto&& arg_elem : m_args) {
// OUT parameters are not considered part of the signature, except for EDB-SPL,
// although this is not true for EDB AS90 onwards..
if (arg_elem.mode != Arg::Out && arg_elem.mode != Arg::Table) {
if (args.length() > 0) {
if (forScript)
args += ",\n";
else
args += ", ";
}
QString typname = types->getByKey(arg_elem.type)->objectName();
if (forScript)
args += " <" + typname + ">";
else
args += typname;
}
}
return args;
}
const QString& PgProc::createSql() const
{
if (createSqlCache.isEmpty()) {
QString sql;
if (isAggregate()) {
/// \todo Support for aggregates
createSqlCache = "-- aggregates not supported yet\n";
return createSqlCache;
}
QString quoted_name = QString("%1(%2)").arg(fullyQualifiedQuotedObjectName(), argListWithNames(true));
QString quoted_sig = QString("%1(%2)").arg(fullyQualifiedQuotedObjectName(), argSigList());
if (isProcedure()) {
// P CREATE [ OR REPLACE ] PROCEDURE
// name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] )
sql = QString("-- Procedure: %1\n\n"
"-- DROP PROCEDURE %1;\n\n"
"CREATE OR REPLACE PROCEDURE %2\n"
).arg(quoted_sig, quoted_name);
}
else {
// all other kinds are functions
// F CREATE [ OR REPLACE ] FUNCTION
// name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] )
// F [ RETURNS rettype
/// \todo F | RETURNS TABLE ( column_name column_type [, ...] ) ]
auto&& types = catalog().types();
QString return_type = types->getByKey(rettype)->objectName();
sql = QString("-- Function: %1\n\n"
"-- DROP FUNCTION %1;\n\n"
"CREATE OR REPLACE FUNCTION %2\n"
" RETURNS %3"
).arg(quoted_sig, quoted_name, return_type);
}
sql += " AS\n";
auto language = catalog().languages()->getByKey(lang);
BOOST_ASSERT(language != nullptr);
if (language->isC()) {
// | AS 'obj_file', 'link_symbol'
sql += escapeLiteral(bin) % ", " % escapeLiteral(src);
}
else {
// | AS 'definition'
sql += dollarQuoteString(src);
}
// { LANGUAGE lang_name
sql += "\n LANGUAGE " % language->quotedObjectName();
/// \todo | TRANSFORM { FOR TYPE type_name } [, ... ]
// | WINDOW
if (isWindow())
sql += " WINDOW";
if (!isProcedure()) {
// F | IMMUTABLE | STABLE | VOLATILE | [ NOT ] LEAKPROOF
sql += " " % volatility();
if (leakproof)
sql += " LEAKPROOF";
// F | CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
// CALLED ON NULL INPUT is the default so we never specify this
// RETURNS NULL ON NULL INPUT is an alias for STRICT so we use STRICT
if (isstrict)
sql += " STRICT"
;
/// \todo F | [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
// if (GetSecureDefiner())
// sql += wxT(" SECURITY DEFINER");
// F | PARALLEL { UNSAFE | RESTRICTED | SAFE }
sql += " " % parallelModeToSql(parallel);
// F | COST execution_cost
sql += QString("\n COST %1").arg(static_cast<double>(cost));
// F | ROWS result_rows
if (retset)
sql += QString("n ROWS %1").arg(static_cast<double>(rows));
}
sql += ";";
// | SET configuration_parameter { TO value | = value | FROM CURRENT }
for (auto&& cfg : config) {
auto before = cfg.section('=', 0, 0); // before first =
auto after = cfg.section('=', 1, -1); // everything after first =
if (before != "search_path" && before != "temp_tablespaces")
sql += QString("\nALTER FUNCTION %1 SET %2 TO '%3'").arg(quoted_sig, before, after);
else
sql += QString("\nALTER FUNCTION %1 SET %2 TO %3").arg(quoted_sig, before, after);
}
// sql += wxT("\n")
// + GetGrant(wxT("X"), wxT("FUNCTION ") + qtSig);
sql += alterOwnerSql("FUNCTION " + quoted_sig);
sql += grantSql();
// if (!GetComment().IsNull())
// {
// sql += wxT("COMMENT ON FUNCTION ") + qtSig
// + wxT(" IS ") + qtDbString(GetComment()) + wxT(";\n");
// }
// if (GetConnection()->BackendMinimumVersion(9, 1))
// sql += GetSeqLabelsSql();
createSqlCache = std::move(sql);
}
return createSqlCache;
}
QString PgProc::volatility() const
{
switch (provolatile) {
case 'i': return "IMMUTABLE";
case 's': return "STABLE";
case 'v': return "VOLATILE";
}
return "<unknown>";
}
QString PgProc::typeName() const
{
switch (kind) {
case ProcKind::Function:
case ProcKind::Window:
return "FUNCTION";
case ProcKind::Procedure:
return "PROCEDURE";
case ProcKind::Aggregate:
return "AGGREGATE";
}
throw std::runtime_error("Unexpected kind in PgProc::typeName()");
}
QString PgProc::aclAllPattern() const
{
// X = execute
return "X";
}