pgLab/pglablib/catalog/PgProc.cpp
eelke 43f8117bbd pg11: pg_proc, type of function is stored differently from pg11 forward
Followed the more structured approach of pg11 in combining the different types into a kind field
when reading from older versions we migrate the old fields to the new field. Change in 11
is because of PROCEDURE support. However full PROCEDURE support will be its own change and is
registered as issue #37
2018-12-23 08:48:45 +01:00

320 lines
8.2 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;
}
}
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;
}
QString PgProc::createSql() const
{
if (createSqlCache.isEmpty()) {
QString sql;
//wxString qtName = GetQuotedFullIdentifier() + wxT("(") + GetArgListWithNames(true) + wxT(")");
QString quoted_name = QString("%1(%2)").arg(fullyQualifiedQuotedObjectName(), argListWithNames(true));
// wxString qtSig = GetQuotedFullIdentifier() + wxT("(") + GetArgSigList() + wxT(")");
QString quoted_sig = QString("%1(%2)").arg(fullyQualifiedQuotedObjectName(), argSigList());
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 AS\n"
).arg(quoted_sig, quoted_name, return_type);
// if (GetLanguage().IsSameAs(wxT("C"), false))
// {
// sql += qtDbString(GetBin()) + wxT(", ") + qtDbString(GetSource());
// }
// else
// {
// if (GetConnection()->BackendMinimumVersion(7, 5))
// sql += qtDbStringDollar(GetSource());
// else
// sql += qtDbString(GetSource());
// }
QString language;
{
auto l = catalog().languages()->getByKey(lang);
if (l)
language = l->objectName();
}
if (language == "c") {
sql += escapeLiteral(bin) % ", " % escapeLiteral(src);
}
else {
sql += dollarQuoteString(src);
}
// sql += wxT("\n LANGUAGE ") + GetLanguage() + wxT(" ");
sql += "\n LANGUAGE " % language % " ";
// if (GetConnection()->BackendMinimumVersion(8, 4) && GetIsWindow())
// sql += wxT("WINDOW ");
if (isWindow())
sql += "WINDOW ";
// sql += GetVolatility();
sql += volatility();
if (leakproof)
sql += " LEAKPROOF";
if (isstrict)
sql += " STRICT";
// if (GetSecureDefiner())
// sql += wxT(" SECURITY DEFINER");
sql += QString("\n COST %1").arg(cost);
if (retset)
sql += QString("n ROWS %1").arg(rows);
sql += ";";
// if (!sql.Strip(wxString::both).EndsWith(wxT(";")))
// sql += wxT(";");
// size_t i;
// for (i = 0 ; i < configList.GetCount() ; i++)
// {
// if (configList.Item(i).BeforeFirst('=') != wxT("search_path") &&
// configList.Item(i).BeforeFirst('=') != wxT("temp_tablespaces"))
// sql += wxT("\nALTER FUNCTION ") + qtSig
// + wxT(" SET ") + configList.Item(i).BeforeFirst('=') + wxT("='") + configList.Item(i).AfterFirst('=') + wxT("';\n");
// else
// sql += wxT("\nALTER FUNCTION ") + qtSig
// + wxT(" SET ") + configList.Item(i).BeforeFirst('=') + wxT("=") + configList.Item(i).AfterFirst('=') + wxT(";\n");
// }
// sql += wxT("\n")
// + GetOwnerSql(8, 0, wxT("FUNCTION ") + qtSig)
// + GetGrant(wxT("X"), wxT("FUNCTION ") + qtSig);
// 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>";
}