2018-11-17 19:38:07 +01:00
|
|
|
|
#include "PgProc.h"
|
2018-11-25 09:06:01 +01:00
|
|
|
|
#include "std_utils.h"
|
|
|
|
|
|
#include "SqlFormattingUtils.h"
|
|
|
|
|
|
#include "PgDatabaseCatalog.h"
|
2018-12-22 13:52:19 +01:00
|
|
|
|
#include "PgLanguageContainer.h"
|
2018-11-25 09:06:01 +01:00
|
|
|
|
#include "PgTypeContainer.h"
|
2018-12-22 13:52:19 +01:00
|
|
|
|
#include <QStringBuilder>
|
2018-11-25 09:06:01 +01:00
|
|
|
|
#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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
2018-11-17 19:38:07 +01:00
|
|
|
|
|
2018-11-25 09:06:01 +01:00
|
|
|
|
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 += " ";
|
2018-12-16 09:24:27 +01:00
|
|
|
|
arg += types->getByKey(arg_elem.type)->objectName();
|
2018-11-25 09:06:01 +01:00
|
|
|
|
|
|
|
|
|
|
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 += ", ";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-12-16 09:24:27 +01:00
|
|
|
|
QString typname = types->getByKey(arg_elem.type)->objectName();
|
2018-11-25 09:06:01 +01:00
|
|
|
|
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();
|
2018-12-16 09:24:27 +01:00
|
|
|
|
QString return_type = types->getByKey(rettype)->objectName();
|
2018-11-25 09:06:01 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
|
// }
|
2018-12-22 13:52:19 +01:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2018-11-25 09:06:01 +01:00
|
|
|
|
// sql += wxT("\n LANGUAGE ") + GetLanguage() + wxT(" ");
|
2018-12-22 13:52:19 +01:00
|
|
|
|
sql += "\n LANGUAGE " % language % " ";
|
2018-11-25 09:06:01 +01:00
|
|
|
|
// if (GetConnection()->BackendMinimumVersion(8, 4) && GetIsWindow())
|
|
|
|
|
|
// sql += wxT("WINDOW ");
|
2018-12-22 13:52:19 +01:00
|
|
|
|
if (iswindow)
|
|
|
|
|
|
sql += "WINDOW ";
|
2018-11-25 09:06:01 +01:00
|
|
|
|
// sql += GetVolatility();
|
2018-12-22 13:52:19 +01:00
|
|
|
|
sql += volatility();
|
|
|
|
|
|
|
|
|
|
|
|
if (leakproof)
|
|
|
|
|
|
sql += " LEAKPROOF";
|
|
|
|
|
|
if (isstrict)
|
|
|
|
|
|
sql += " STRICT";
|
2018-11-25 09:06:01 +01:00
|
|
|
|
|
|
|
|
|
|
// if (GetSecureDefiner())
|
|
|
|
|
|
// sql += wxT(" SECURITY DEFINER");
|
|
|
|
|
|
|
2018-12-22 13:52:19 +01:00
|
|
|
|
sql += QString("\n COST %1").arg(cost);
|
|
|
|
|
|
if (retset)
|
|
|
|
|
|
sql += QString("n ROWS %1").arg(rows);
|
|
|
|
|
|
sql += ";";
|
2018-11-25 09:06:01 +01:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
2018-12-22 13:52:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QString PgProc::volatility() const
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (provolatile) {
|
|
|
|
|
|
case 'i': return "IMMUTABLE";
|
|
|
|
|
|
case 's': return "STABLE";
|
|
|
|
|
|
case 'v': return "VOLATILE";
|
|
|
|
|
|
}
|
|
|
|
|
|
return "<unknown>";
|
|
|
|
|
|
}
|