text: /*
    Copyright (c) 2004-2016 The FlameRobin Development Team
  
    Permission is hereby granted, free of charge, to any person obtaining
    a copy of this software and associated documentation files (the
    "Software"), to deal in the Software without restriction, including
    without limitation the rights to use, copy, modify, merge, publish,
    distribute, sublicense, and/or sell copies of the Software, and to
    permit persons to whom the Software is furnished to do so, subject to
    the following conditions:
  
    The above copyright notice and this permission notice shall be included
    in all copies or substantial portions of the Software.
  
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  */
  
  // For compilers that support precompilation, includes "wx/wx.h".
  #include "wx/wxprec.h"
  
  // for all others, include the necessary headers (this file is usually all you
  // need because it includes almost all "standard" wxWindows headers
  #ifndef WX_PRECOMP
      #include "wx/wx.h"
  #endif
  
  // needed for platform independent EOL
  #include <wx/textbuf.h>
  
  #include <ibpp.h>
  
  #include "core/StringUtils.h"
  #include "engine/MetadataLoader.h"
  #include "frutils.h"
  #include "gui/AdvancedMessageDialog.h"
  #include "metadata/database.h"
  #include "metadata/domain.h"
  #include "metadata/function.h"
  #include "metadata/MetadataItemVisitor.h"
  #include "metadata/parameter.h"
  #include "sql/StatementBuilder.h"
  
  
  Function::Function(DatabasePtr database, const wxString& name)
      : MetadataItem(ntFunction, database.get(), name)
  {
      ensurePropertiesLoaded();
  }
  
  Function::Function(MetadataItem* parent, const wxString& name)
      : MetadataItem(ntFunction, parent, name)
  {
      ensurePropertiesLoaded();
  }
  
  void Function::loadChildren()
  {
      bool childrenWereLoaded = childrenLoaded();
      // in case an exception is thrown this should be repeated
      setChildrenLoaded(false);
  
      DatabasePtr db = getDatabase();
      MetadataLoader* loader = db->getMetadataLoader();
      // first start a transaction for metadata loading, then lock the procedure
      // when objects go out of scope and are destroyed, procedure will be
      // unlocked before the transaction is committed - any update() calls on
      // observers can possibly use the same transaction
      // when objects go out of scope and are destroyed, object will be unlocked
      // before the transaction is committed - any update() calls on observers
      // can possibly use the same transaction
      MetadataLoaderTransaction tr(loader);
      SubjectLocker lock(db.get());
      wxMBConv* converter = db->getCharsetConverter();
  
  
      std::string sql(
          "select a.rdb$argument_name, a.rdb$field_source, " //1..2
          "a.rdb$mechanism, a.rdb$field_type, a.rdb$field_scale, a.rdb$field_length, " //3..6
          "a.rdb$field_sub_type, a.rdb$field_precision, "//7..8
          "f.rdb$return_argument, a.rdb$argument_position, " //9..10
      );
      if (db->getInfo().getODSVersionIsHigherOrEqualTo(12, 0))
          sql += "rdb$default_source, rdb$null_flag, rdb$argument_mechanism, rdb$field_name, rdb$relation_name, "; //11..13
      else
          sql += "null, null, -1, null, null, ";
      sql += "a.rdb$description from rdb$function_arguments a "
          " join rdb$functions f on f.rdb$function_name = a.rdb$function_name "
          "where a.rdb$function_name = ? ";
      if (getParent()->getType() == ntDatabase) {
          sql += db->getInfo().getODSVersionIsHigherOrEqualTo(12, 0) ? " and a.rdb$package_name is null " : " ";
      }
      sql += "order by a.rdb$argument_position ";
   //    sql += "order by iif(a.rdb$argument_name is null, 2014, a.rdb$argument_position) ";
  
      IBPP::Statement st1 = loader->getStatement(sql);
      st1->Set(1, wx2std(getName_(), converter));
      st1->Execute();
  
      ParameterPtrs parameters;
      while (st1->Fetch())
      {
          std::string s;
          
          short returnarg, retpos;
          st1->Get(9, returnarg);
          st1->Get(10, retpos);
  
          if (!st1->IsNull(1)) {
              st1->Get(1, s);
          }
          else {
              s = (returnarg == retpos) ? "RETURN " : "";
          }
          wxString param_name(std2wxIdentifier(s, converter));
          if (!st1->IsNull(2)) {
              st1->Get(2, s);
          }
          else {
              if (!st1->IsNull(4)) {
                  short  type, scale, length, subtype, precision;
                  st1->Get(4, type);
                  st1->Get(5, scale);
                  st1->Get(6, length);
                  st1->Get(7, subtype);
                  st1->Get(8, precision);
                  s = Domain::dataTypeToString(type, scale, precision, subtype, length);
              }
          }
          wxString source(std2wxIdentifier(s, converter));
  
          short partype, mechanism = -1;
          partype = (returnarg == retpos) ? 1 : 0;
          /*if (st1->IsNull(3)) {
              partype = st1->IsNull(1) ? 1 : 0;
          }
          else {
              st1->Get(3, partype);
          }*/
          bool hasDefault = !st1->IsNull(11);
          wxString defaultSrc;
          if (hasDefault)
          {
              st1->Get(11, s);
              defaultSrc = std2wxIdentifier(s, converter);
          }
          bool notNull = false;
          if (!st1->IsNull(12))
              st1->Get(12, &notNull);
          if (!st1->IsNull(13)) {
              st1->Get(13, mechanism);
          }
          else {
              if (!st1->IsNull(3))
                  st1->Get(3, mechanism);
          }
          wxString field;
          if (!st1->IsNull(14)) {
              st1->Get(14, s);
              field = std2wxIdentifier(s, converter);
          }
          wxString relation;
          if (!st1->IsNull(15)) {
              st1->Get(15, s);
              relation = std2wxIdentifier(s, converter);
          }
  
          bool hasDescription = !st1->IsNull(16);
  
          ParameterPtr par = findParameter(param_name);
          if (!par)
          {
              par.reset(new Parameter(this, param_name));
              initializeLockCount(par, getLockCount());
          }
          parameters.push_back(par);
          par->initialize(source, partype, mechanism, !notNull, defaultSrc,
              hasDefault, hasDescription, relation, field);
      }
  
      setChildrenLoaded(true);
      if (!childrenWereLoaded || parametersM != parameters)
      {
          parametersM.swap(parameters);
          notifyObservers();
      }
  }
  
  bool Function::getChildren(std::vector<MetadataItem*>& temp)
  {
      if (parametersM.empty())
          return false;
      std::transform(parametersM.begin(), parametersM.end(),
          std::back_inserter(temp), std::mem_fn(&ParameterPtr::get));
      return !parametersM.empty();
  }
  
  void Function::lockChildren()
  {
      std::for_each(parametersM.begin(), parametersM.end(),
          std::mem_fn(&Parameter::lockSubject));
  }
  
  void Function::unlockChildren()
  {
  	std::for_each(parametersM.begin(), parametersM.end(),
  		std::mem_fn(&Parameter::unlockSubject));
  }
  
  ParameterPtrs::iterator Function::begin()
  {
  	// please - don't load here
  	// this code is used to get columns we want to alert about changes
  	// but if there aren't any columns, we don't want to waste time
  	// loading them
  	return parametersM.begin();
  }
  
  ParameterPtrs::iterator Function::end()
  {
  	// please see comment for begin()
  	return parametersM.end();
  }
  
  ParameterPtrs::const_iterator Function::begin() const
  {
  	return parametersM.begin();
  }
  
  ParameterPtrs::const_iterator Function::end() const
  {
  	return parametersM.end();
  }
  
  ParameterPtr Function::findParameter(const wxString& name) const
  {
  	for (ParameterPtrs::const_iterator it = parametersM.begin();
  		it != parametersM.end(); ++it)
  	{
  		if ((*it)->getName_() == name)
  			return *it;
  	}
  	return ParameterPtr();
  }
  
  size_t Function::getParamCount() const
  {
  	return parametersM.size();
  }
  
  wxString Function::getOwner()
  {
  	DatabasePtr db = getDatabase();
  	if (db->getInfo().getODSVersionIsHigherOrEqualTo(12, 0))
  	{
  		MetadataLoader* loader = db->getMetadataLoader();
  		MetadataLoaderTransaction tr(loader);
  		wxMBConv* converter = db->getCharsetConverter();
  		std::string sql(
  			"select rdb$owner_name from rdb$functions where rdb$function_name = ?"
  		     " and rdb$package_name is null ");
  		IBPP::Statement st1 = loader->getStatement(sql);
  		st1->Set(1, wx2std(getName_(), converter));
  		st1->Execute();
  		st1->Fetch();
  		std::string name;
  		st1->Get(1, name);
  		return std2wxIdentifier(name, converter);
  	}
  	else
  	{
  		return wxString();
  	}
  }
  
  wxString Function::getSqlSecurity()
  {
  	DatabasePtr db = getDatabase();
  	if (db->getInfo().getODSVersionIsHigherOrEqualTo(13, 0))
  	{
  		MetadataLoader* loader = db->getMetadataLoader();
  		MetadataLoaderTransaction tr(loader);
  		wxMBConv* converter = db->getCharsetConverter();
  		std::string sql(
  			"select rdb$sql_security from rdb$functions where rdb$function_name = ?"
  			" and rdb$package_name is null ");
  		IBPP::Statement st1 = loader->getStatement(sql);
  		st1->Set(1, wx2std(getName_(), converter));
  		st1->Execute();
  		st1->Fetch();
  		bool b;
  		st1->Get(1, b);
  		return wxString(b ? "SQL SECURITY DEFINER" : "SQL SECURITY INVOKER");
  	}
  	else
  	{
  		return wxString();
  	}
  }
  
  std::vector<Privilege>* Function::getPrivileges()
  {
  	// load privileges from database and return the pointer to collection
  	DatabasePtr db = getDatabase();
  	MetadataLoader* loader = db->getMetadataLoader();
  	// first start a transaction for metadata loading, then lock the function
  	// when objects go out of scope and are destroyed, procedure will be
  	// unlocked before the transaction is committed - any update() calls on
  	// observers can possibly use the same transaction
  	MetadataLoaderTransaction tr(loader);
  	SubjectLocker lock(this);
  	wxMBConv* converter = db->getCharsetConverter();
  
  	privilegesM.clear();
  
  	IBPP::Statement st1 = loader->getStatement(
  		"select RDB$USER, RDB$USER_TYPE, RDB$GRANTOR, RDB$PRIVILEGE, "
  		"RDB$GRANT_OPTION, RDB$FIELD_NAME "
  		"from RDB$USER_PRIVILEGES "
  		"where RDB$RELATION_NAME = ? and rdb$object_type = 15 "
  		"order by rdb$user, rdb$user_type, rdb$privilege"
  	);
  	st1->Set(1, wx2std(getName_(), converter));
  	st1->Execute();
  	std::string lastuser;
  	int lasttype = -1;
  	Privilege* pr = 0;
  	while (st1->Fetch())
  	{
  		std::string user, grantor, privilege, field;
  		int usertype, grantoption = 0;
  		st1->Get(1, user);
  		st1->Get(2, usertype);
  		st1->Get(3, grantor);
  		st1->Get(4, privilege);
  		if (!st1->IsNull(5))
  			st1->Get(5, grantoption);
  		st1->Get(6, field);
  		if (!pr || user != lastuser || usertype != lasttype)
  		{
  			Privilege p(this, std2wxIdentifier(user, converter),
  				usertype);
  			privilegesM.push_back(p);
  			pr = &privilegesM.back();
  			lastuser = user;
  			lasttype = usertype;
  		}
  		pr->addPrivilege(privilege[0], wxString(grantor.c_str(), *converter),
  			grantoption == 1);
  	}
  	return &privilegesM;
  }
  
  
  wxString Function::getDefinition()
  {
  	ensureChildrenLoaded();
  	wxString collist, parlist;
  	ParameterPtrs::const_iterator lastInput, lastOutput;
  	for (ParameterPtrs::const_iterator it = parametersM.begin();
  		it != parametersM.end(); ++it)
  	{
  		if ((*it)->isOutputParameter())
  			lastOutput = it;
  		else
  			lastInput = it;
  	}
  	for (ParameterPtrs::const_iterator it =
  		parametersM.begin(); it != parametersM.end(); ++it)
  	{
  		// No need to quote domains, as currently only regular datatypes can be
  		// used for Function parameters
  		if ((*it)->isOutputParameter())
  		{
  			collist += "    " + (*it)->getQuotedName() + " "
  				+ (*it)->getDomain()->getDatatypeAsString();
  			if (it != lastOutput)
  				collist += ",";
  			collist += "\n";
  		}
  		else
  		{
  			parlist += "    " + (*it)->getQuotedName() + " "
  				+ (*it)->getDomain()->getDatatypeAsString();
  			if (it != lastInput)
  				parlist += ",";
  			parlist += "\n";
  		}
  	}
  	wxString retval = getQuotedName();
  	if (!parlist.empty())
  		retval += "(\n" + parlist + ")";
  	retval += "\n";
  	if (!collist.empty())
  		retval += "returns:\n" + collist;
  	return retval;
  }
  
  void Function::acceptVisitor(MetadataItemVisitor* visitor)
  {
      visitor->visitMetadataItem(*this); 
  }
  
  void Function::checkDependentFunction()
  {
  	// check dependencies and parameters
  	ensureChildrenLoaded();
  	std::vector<Dependency> deps;
  	getDependencies(deps, false);
  	// if there is a dependency, but parameter doesn't exist, warn the user
  	int count = 0;
  	wxString missing;
  	for (std::vector<Dependency>::iterator it = deps.begin();
  		it != deps.end(); ++it)
  	{
  		std::vector<DependencyField> fields;
  		(*it).getFields(fields);
  		for (std::vector<DependencyField>::const_iterator ci = fields.begin();
  			ci != fields.end(); ++ci)
  		{
  			bool found = false;
  			for (ParameterPtrs::iterator i2 = begin();
  				i2 != end(); ++i2)
  			{
                  if ((*i2)->getName_() == (ci->getName_()))
  				{
  					found = true;
  					break;
  				}
  			}
  			if (!found && ++count < 20)
  			{
  				missing += wxString::Format(
  					_("Function %s depends on parameter %s.%s"),
  					(*it).getName_().c_str(),
                      (ci->getName_()).c_str(),
  					wxTextBuffer::GetEOL()
  				);
  			}
  		}
  	}
  	if (count > 0)
  	{
  		if (count > 19)
  		{
  			missing += wxTextBuffer::GetEOL()
  				+ wxString::Format(_("%d total dependencies (20 shown)."), count);
  		}
  		showWarningDialog(0,
  			_("Dependencies broken"),
  			wxString::Format(
  				_("Some other functions depend on %s:%s%s%s"),
  				getName_().c_str(),
  				wxTextBuffer::GetEOL(),
  				wxTextBuffer::GetEOL(),
  				missing.c_str()),
  			AdvancedMessageDialogButtonsOk()
  		);
  	}
  
  }
  
  
  FunctionSQL::FunctionSQL(DatabasePtr database, const wxString & name)
  	: Function(database, name)
  {
  }
  
  FunctionSQL::FunctionSQL(MetadataItem* parent, const wxString& name)
      : Function(parent, name)
  {
  }
  
  void FunctionSQL::loadProperties()
  {
  }
  
  wxString FunctionSQL::getSource()
  {
  	DatabasePtr db = getDatabase();
  	MetadataLoader* loader = db->getMetadataLoader();
  	MetadataLoaderTransaction tr(loader);
  	wxMBConv* converter = db->getCharsetConverter();
  	std::string sql = "select rdb$function_source, ";
  	sql += db->getInfo().getODSVersionIsHigherOrEqualTo(12, 0) ? "rdb$entrypoint, rdb$engine_name  " : "null, null ";
  	sql += "from rdb$functions where rdb$function_name = ?";
  	sql += " and rdb$package_name is null ";
  	IBPP::Statement st1 = loader->getStatement(sql);
  	st1->Set(1, wx2std(getName_(), converter));
  	st1->Execute();
  	st1->Fetch();
  	wxString source;
  	if (!st1->IsNull(2))
  	{
  		std::string s;
  		st1->Get(2, s);
  		source += "EXTERNAL NAME '" + std2wxIdentifier(s, converter) + "'\n";
  		if (!st1->IsNull(3))
  		{
  			s.clear();
  			st1->Get(3, s);
  			source += "ENGINE " + std2wxIdentifier(s, converter) + "\n";
  			if (!st1->IsNull(1))
  			{
  				wxString source1;
  				readBlob(st1, 1, source1, converter);
  				source1.Trim(false);     // remove leading whitespace
  				source += "\nAS\n" + source1 + "\n";
  			}
  		}
  	}
  	else
  	{
  		wxString source1;
  		readBlob(st1, 1, source1, converter);
  		source1.Trim(false);     // remove leading whitespace
  		source += "\nAS\n" + source1 + "\n";
  	}
  	return source;
  }
  
  wxString FunctionSQL::getAlterSql(bool full)
  {
  	ensureChildrenLoaded();
  
  	DatabasePtr db = getDatabase();
  
  	wxString sql = "SET TERM ^ ;\nALTER FUNCTION " + getQuotedName();
  	if (getParamCount() > 0)
  	{
  		wxString input, output;
  		for (ParameterPtrs::const_iterator it = begin();
  			it != end(); ++it)
  		{
  			wxString charset;
  			wxString param = (*it)->isOutputParameter() ? "" : (*it)->getQuotedName() + " ";
  			DomainPtr dm = (*it)->getDomain();
              if ((*it)->getMechanism() == 1) {
                  param += (*it)->getTypeOf();
              }
              else
                  if (dm)
  			{
  				if (dm->isSystem()) // autogenerated domain -> use datatype
  				{
  					param += dm->getDatatypeAsString();
  					charset = dm->getCharset();
  					if (!charset.empty())
  					{
  						if (charset != db->getDatabaseCharset())
  							charset = " CHARACTER SET " + charset;
  						else
  							charset.clear();
  					}
  				}
  				else
  				{
                      if ((*it)->getMechanism() == 1)
                          param += param += (*it)->getTypeOf();
                      else
                          param += dm->getQuotedName();
  				}
  			}
  			else
  				param += (*it)->getSource();
  
  			if ((*it)->isOutputParameter())
  			{
  				if (output.empty())
  					output += "\nRETURNS   ";
  				else
  					output += ",\n    ";
  				output += param + charset;
  				if (!(*it)->isNullable(IgnoreDomainNullability))
  					output += " NOT NULL";
  			}
  			else
  			{
  				if (input.empty())
  					input += " (\n    ";
  				else
  					input += ",\n    ";
  				input += param;
  				input += charset;
  				if (!(*it)->isNullable(IgnoreDomainNullability))
  					input += " NOT NULL";
  				wxString defaultValue;
  				// default either from parameter itself
  				if ((*it)->getDefault(IgnoreDomainDefault, defaultValue)
  					// or from underlying autogenerated domain
  					|| (dm && dm->isSystem() && dm->getDefault(defaultValue)))
  				{
  					input += " DEFAULT " + defaultValue;
  				}
  			}
  		}
  
  		if (!input.empty())
  			sql += input + " )";
  		if (!output.empty())
  			sql += output ;
  	}
  	sql += getSqlSecurity() + "\n";
  	if (full)
  		sql += getSource();
  	else
  		sql += "BEGIN SUSPEND; END";
  	sql += "^\nSET TERM ; ^\n";
  	return sql;
  }
  
  wxString FunctionSQL::getDefinition()
  {
  	return wxString("Function SQL");
  }
  
  const wxString FunctionSQL::getTypeName() const
  {
  	return wxString("FUNCTION");
  }
  
  void FunctionSQL::acceptVisitor(MetadataItemVisitor * visitor)
  {
  	visitor->visitFunctionSQL(*this);
  }
  
  wxString FunctionSQL::getQuotedName() const
  {
      if (getParent()->getType() == ntDatabase)
          return MetadataItem::getQuotedName();
      else
          return getParent()->getQuotedName() + '.' + MetadataItem::getQuotedName();
  }
  
  
  // Functions SQL collection
  FunctionSQLs::FunctionSQLs(DatabasePtr database)
  	: MetadataCollection<FunctionSQL>(ntFunctionSQLs, database, _("Functions"))
  {
  }
  
  void FunctionSQLs::acceptVisitor(MetadataItemVisitor* visitor)
  {
  	visitor->visitFunctionSQLs(*this);
  }
  
  void FunctionSQLs::load(ProgressIndicator* progressIndicator)
  {
  	DatabasePtr db = getDatabase();
  	if (db->getInfo().getODSVersionIsHigherOrEqualTo(12, 0))
  	{
  		wxString stmt = "select rdb$function_name from rdb$functions"
  		" where (rdb$system_flag = 0 or rdb$system_flag is null)";
  		stmt += " and RDB$LEGACY_FLAG = 0  and rdb$package_name is null ";
  		stmt += " order by 1";
  		setItems(db->loadIdentifiers(stmt, progressIndicator));
  	}
  }
  
  void FunctionSQLs::loadChildren()
  {
  	load(0);
  }
  
  const wxString FunctionSQLs::getTypeName() const
  {
  	return "FUNCTIONSQL_COLLECTION";
  }
  
  
  void UDF::loadProperties()
  {
  	setPropertiesLoaded(false);
  		wxString mechanismNames[] = { "value", "reference",
  			"descriptor", "blob descriptor", "scalar array",
  			"null", wxEmptyString };
  		wxString mechanismDDL[] = { " BY VALUE ", wxEmptyString,
  			" BY DESCRIPTOR ", wxEmptyString, " BY SCALAR ARRAY ",
  			" NULL ", wxEmptyString };
  
  		bool first = true;
  		wxString retstr;
  		definitionM = getName_() + "(" + wxTextBuffer::GetEOL();
  		paramListM = wxEmptyString;
  
  		DatabasePtr db = getDatabase();
  		MetadataLoader* loader = db->getMetadataLoader();
  		wxMBConv* converter = db->getCharsetConverter();
  		MetadataLoaderTransaction tr(loader);
  
  		std::string stmt = "SELECT f.RDB$RETURN_ARGUMENT, a.RDB$MECHANISM,"
  			" a.RDB$ARGUMENT_POSITION, a.RDB$FIELD_TYPE, a.RDB$FIELD_SCALE,"
  			" a.RDB$FIELD_LENGTH, a.RDB$FIELD_SUB_TYPE, a.RDB$FIELD_PRECISION,"
  			" f.RDB$MODULE_NAME, f.RDB$ENTRYPOINT, c.RDB$CHARACTER_SET_NAME, ";
  		if (db->getInfo().getODSVersionIsHigherOrEqualTo(12, 0))
  			stmt += " f.RDB$LEGACY_FLAG ";
  		else
  			stmt += "null ";
  
  		stmt += " FROM RDB$FUNCTIONS f"
  			" LEFT OUTER JOIN RDB$FUNCTION_ARGUMENTS a"
  			" ON f.RDB$FUNCTION_NAME = a.RDB$FUNCTION_NAME"
  			" LEFT OUTER JOIN RDB$CHARACTER_SETS c"
  			" ON a.RDB$CHARACTER_SET_ID = c.RDB$CHARACTER_SET_ID"
  			" WHERE f.RDB$FUNCTION_NAME = ? ";
  		if (db->getInfo().getODSVersionIsHigherOrEqualTo(12, 0))
  			stmt += " and f.rdb$package_name is null";
  		stmt +=	" ORDER BY a.RDB$ARGUMENT_POSITION";
  
  
  		IBPP::Statement& st1 = loader->getStatement(stmt);
  		st1->Set(1, wx2std(getName_(), converter));
  		st1->Execute();
  		while (st1->Fetch())
  		{
  			short returnarg, mechanism, type, scale, length, subtype, precision,
  				retpos;
  			std::string libraryName, entryPoint, charset;
  			st1->Get(1, returnarg);
  			st1->Get(2, mechanism);
  			st1->Get(3, retpos);
  			st1->Get(4, type);
  			st1->Get(5, scale);
  			st1->Get(6, length);
  			st1->Get(7, subtype);
  			st1->Get(8, precision);
  			st1->Get(9, libraryName);
  			libraryNameM = wxString(libraryName.c_str(), *converter).Strip();
  			st1->Get(10, entryPoint);
  			entryPointM = wxString(entryPoint.c_str(), *converter).Strip();
  			wxString datatype = Domain::dataTypeToString(type, scale,
  				precision, subtype, length);
  			if (!st1->IsNull(11))
  			{
  				st1->Get(11, charset);
  				wxString chset = wxString(charset.c_str(), *converter).Strip();
  				if (db->getDatabaseCharset() != chset)
  				{
  					datatype += " " + SqlTokenizer::getKeyword(kwCHARACTER)
  						+ " " + SqlTokenizer::getKeyword(kwSET)
  						+ " " + chset;
  				}
  			}
  			if (type == 261)    // avoid subtype information for BLOB
  				datatype = SqlTokenizer::getKeyword(kwBLOB);
  
  
  			int mechIndex = (mechanism < 0 ? -mechanism : mechanism);
  			if (mechIndex >= (sizeof(mechanismNames)/sizeof(wxString)))
  				mechIndex = (sizeof(mechanismNames)/sizeof(wxString)) - 1;
  			wxString param = "    " + datatype + " "
  				+ SqlTokenizer::getKeyword(kwBY) + " "
  				+ mechanismNames[mechIndex];
  			if (mechanism < 0)
  				param += wxString(" ") + SqlTokenizer::getKeyword(kwFREE_IT);
  			if (returnarg == retpos)    // output
  			{
  				retstr = param;
  				retstrM = datatype + mechanismDDL[mechIndex];
  				if (retpos != 0)
  				{
  					retstrM = SqlTokenizer::getKeyword(kwPARAMETER) + " ";
  					retstrM << retpos;
  					if (!paramListM.IsEmpty())
  						paramListM += ", ";
  					paramListM += datatype + mechanismDDL[mechIndex];
  				}
  				if (mechanism < 0){
  				  retstrM += wxString(" ") + SqlTokenizer::getKeyword(kwFREE_IT);
  				}
  			}
  			else
  			{
  				if (first)
  					first = false;
  				else
  					definitionM += wxString(",") + wxTextBuffer::GetEOL();
  				definitionM += param;
  				if (!paramListM.empty())
  					paramListM += ", ";
  				paramListM += datatype + mechanismDDL[mechIndex];
  			}
  		}
  		definitionM += wxString(wxTextBuffer::GetEOL()) + ")"
  			+ wxTextBuffer::GetEOL() + SqlTokenizer::getKeyword(kwRETURNS)
  			+ ":" + wxTextBuffer::GetEOL() + retstr;
  	
  	setPropertiesLoaded(true);
  
  }
  
  UDF::UDF(DatabasePtr database, const wxString& name)
  	: Function(database, name)
  {
  	ensurePropertiesLoaded();
  }
  
  const wxString UDF::getTypeName() const
  {
  	return "UDF";
  }
  
  wxString UDF::getDropSqlStatement() const
  {
  	StatementBuilder sb;
  	sb << kwDROP << ' ' << kwEXTERNAL << ' ' << kwFUNCTION << ' '
  		<< getQuotedName() << ';';
  	return sb;
  }
  
  
  wxString UDF::getCreateSql()
  {
  	ensurePropertiesLoaded();
  	StatementBuilder sb;
  	    sb << kwDECLARE << ' ' << kwEXTERNAL << ' ' << kwFUNCTION << ' '
  			<< getQuotedName() << StatementBuilder::NewLine
  			<< paramListM  << StatementBuilder::NewLine
  			<< kwRETURNS << ' ' << retstrM << StatementBuilder::NewLine
  			<< kwENTRY_POINT << " '" << entryPointM << '\''
  			<< StatementBuilder::NewLine
  			<< kwMODULE_NAME << " '" << libraryNameM << "\';"
  			<< StatementBuilder::NewLine;
  	return sb;
  }
  
  wxString UDF::getDefinition()
  {
  	ensurePropertiesLoaded();
  	return definitionM;
  }
  
  wxString UDF::getLibraryName()
  {
  	ensurePropertiesLoaded();
  	return libraryNameM;
  }
  
  wxString UDF::getEntryPoint()
  {
  	ensurePropertiesLoaded();
  	return entryPointM;
  }
  
  void UDF::acceptVisitor(MetadataItemVisitor* visitor)
  {
  	visitor->visitUDF(*this);
  }
  
  wxString UDF::getSource()
  {
  	ensurePropertiesLoaded();
  	return definitionM;
  }
  
  
  // Functions UDF collection
  UDFs::UDFs(DatabasePtr database)
  	: MetadataCollection<UDF>(ntUDFs, database, _("UDFs"))
  {
  }
  
  void UDFs::acceptVisitor(MetadataItemVisitor* visitor)
  {
  	visitor->visitUDFs(*this);
  }
  
  void UDFs::load(ProgressIndicator* progressIndicator)
  {
  	DatabasePtr db = getDatabase();
  	wxString stmt = "select rdb$function_name from rdb$functions "
  		" where (rdb$system_flag = 0 or rdb$system_flag is null) ";
  	if (db->getInfo().getODSVersionIsHigherOrEqualTo(12, 0))
  		stmt += " and RDB$LEGACY_FLAG = 1 and rdb$package_name is null ";
  	stmt += " order by 1";
  	setItems(db->loadIdentifiers(stmt, progressIndicator));
  }
  
  void UDFs::loadChildren()
  {
  	load(0);
  }
  
  const wxString UDFs::getTypeName() const
  {
  	return "UDF_COLLECTION";
  }
  