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
320 lines
8.2 KiB
C++
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>";
|
|
}
|