#include "PgProc.h" #include "std_utils.h" #include "SqlFormattingUtils.h" #include "PgDatabaseCatalog.h" #include "PgLanguageContainer.h" #include "PgTypeContainer.h" #include #include 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 getArrayFromCommaSeparatedList(const QString &str) { std::vector 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 PgProc::setArgs( std::vector argtypes, std::vector allargtypes, std::vector argmodes, std::vector argnames, std::optional argdefaults ) { // When all args are of type IN allargtypes is empty and only argtypes is filled const std::vector &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& 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 ""; }