#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 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 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; 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(cost)); // F | ROWS result_rows if (retset) sql += QString("n ROWS %1").arg(static_cast(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 ""; } 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"; }