//+=============================================================================
//
// file :         DataBaseUtils.cpp
//
// description :  Various utility for the DataBase.
//
// project :      TANGO Device Servers
//
// author(s) :    A.Gotz, P.Verdier, JL Pons
//
// Copyright (C) :      2004,2005,2006,2007,2008,2009,2010,2011,2012,2013
//						European Synchrotron Radiation Facility
//                      BP 220, Grenoble 38043
//                      FRANCE
//
// This file is part of Tango.
//
// Tango is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Tango is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Tango.  If not, see <http://www.gnu.org/licenses/>.
//
//=============================================================================

#include <tango/tango.h>
#include "DataBase.h"
#include "DataBaseClass.h"
#include "DatabaseConnectionHandle.h"
#include "DatabaseConnectionPool.h"
#include "Logging.h"

#include <errmsg.h>

#include <stdio.h>

#include <chrono>
#include <thread>

namespace DataBase_ns
{

//+----------------------------------------------------------------------------
//
// method : 		DataBase::replace_wildcard(char *wildcard_c_str)
//
// description : 	utility method to replace all occurrences of
//			wildcards (*) with SQL wildcards % and to escape
//			all occurrences	of '%' and '_' with '\'
//
// in :			string **wildcard_c_str - wildcard C string
//
// out :		void - nothing
//
//-----------------------------------------------------------------------------
std::string DataBase::replace_wildcard(const char *wildcard_c_str)
{
    std::string wildcard(wildcard_c_str);
    std::string::size_type index;

    DEBUG_STREAM << "DataBase::replace_wildcard() wildcard in " << wildcard << std::endl;
    // escape %

    index = 0;
    while((index = wildcard.find('%', index)) != std::string::npos)
    {
        wildcard.insert(index, 1, '\\');
        index = index + 2;
    }

    // escape _

    index = 0;
    while((index = wildcard.find('_', index)) != std::string::npos)
    {
        wildcard.insert(index, 1, '\\');
        index = index + 2;
    }

    // escape "

    index = 0;
    while((index = wildcard.find('"', index)) != std::string::npos)
    {
        wildcard.insert(index, 1, '\\');
        index = index + 2;
    }

    // escape '

    index = 0;
    while((index = wildcard.find('\'', index)) != std::string::npos)
    {
        wildcard.insert(index, 1, '\\');
        index = index + 2;
    }

    // replace wildcard * with %

    while((index = wildcard.find('*')) != std::string::npos)
    {
        wildcard.replace(index, 1, 1, '%');
    }

    DEBUG_STREAM << "DataBase::replace_wildcard() wildcard out " << wildcard << std::endl;
    return wildcard;
}

//+----------------------------------------------------------------------------
//
// method : 		DataBase::escape_string(char *string_c_str)
//
// description : 	utility method to escape all occurrences
//			of ' and " with '\'
//
// in :			const char *string_c_str -  C string to be modified.
//
// out :		string - The result string .
//
//-----------------------------------------------------------------------------
std::string DataBase::escape_string(const char *string_c_str)
{
    std::string escaped_string(string_c_str);
    std::string::size_type index;

    DEBUG_STREAM << "DataBase::escape_string() string in : " << escaped_string << std::endl;

    //	escape bakckslash
    index = 0;
    while((index = escaped_string.find('\\', index)) != std::string::npos)
    {
        //	Check if double backslash already treated by client
        std::string s = escaped_string.substr(index + 1);

        //	Check if another escape sequence treated by client
        if(s.find('\"') == 0 || s.find('\'') == 0)
        {
            index++;
        }
        else
        {
            escaped_string.insert(index, 1, '\\');
            index += 2;
        }
    }

    //	escape "
    index = 0;
    while((index = escaped_string.find('"', index)) != std::string::npos)
    {
        if(index == 0) //	Cannot have '\' at -1 !
        {
            escaped_string.insert(index, 1, '\\');
            index += 2;
        }
        else
        {
            //	Check if double quotes already treated by client
            std::string s = escaped_string.substr(index - 1);
            if(s.find('\\') == 0)
            {
                index++;
            }
            else
            {
                escaped_string.insert(index, 1, '\\');
                index += 2;
            }
        }
    }

    //	escape '
    index = 0;
    while((index = escaped_string.find('\'', index)) != std::string::npos)
    {
        if(index == 0) //	Cannot have '\' at -1 !
        {
            escaped_string.insert(index, 1, '\\');
            index += 2;
        }
        else
        {
            //	Check if simple quotes already treated by client
            std::string s = escaped_string.substr(index - 1);
            if(s.find('\\') == 0)
            {
                index++;
            }
            else
            {
                escaped_string.insert(index, 1, '\\');
                index += 2;
            }
        }
    }

    DEBUG_STREAM << "DataBase::escaped_string() wildcard out : " << escaped_string << std::endl;
    return escaped_string;
}

//+----------------------------------------------------------------------------
//
// method : 		DataBase::device_name_to_dfm(std::string &device_name,
//			          char **domain, char **family, char **member)
//
// description : 	utility function to return domain, family and member
//			from device name. Assumes device name has (optional)
//			protocol and instance stripped off i.e. conforms
//			to domain/family/member
//
// in :			char *devname - device name
//
// out :		bool - true or false
//
//-----------------------------------------------------------------------------
bool DataBase::device_name_to_dfm(std::string &devname, char domain[], char family[], char member[])
{
    std::string::size_type index, index2;

    DEBUG_STREAM << "DataBase::device_name_to_dfm() device name in " << devname << std::endl;

    index = devname.find('/');
    index2 = devname.find('/', index + 1);

    (devname.substr(0, index)).copy(domain, index);
    domain[index] = '\0';

    (devname.substr(index + 1, index2 - index)).copy(family, index2 - index);
    family[index2 - index - 1] = '\0';

    (devname.substr(index2 + 1)).copy(member, devname.length() - index2);
    member[devname.length() - index2 - 1] = '\0';

    DEBUG_STREAM << "DataBase::device_name_to_dfm() domain/family/member out " << domain << "/" << family << "/"
                 << member << std::endl;

    return true;
}

//+----------------------------------------------------------------------------
//
// method : 		DataBase::check_device_name(std::string *device_name)
//
// description : 	utility function to check whether device name conforms
//			to the TANGO naming convention of
//
//			[tango!taco:][//instance/]domain/family/member
//
// in :			string *device_name - device name
//
// out :		bool - true or false
//
//-----------------------------------------------------------------------------
bool DataBase::check_device_name(std::string &device_name_str)
{
    std::string devname(device_name_str);
    std::string::size_type index, index2;

    DEBUG_STREAM << "DataBase::check_device_name(): device_name in " << devname << std::endl;

    // check there are no special characters which could be interpreted
    // as wildcards or which are otherwise excluded

    if(devname.find('*') != std::string::npos)
    {
        return false;
    }

    // check protocol - "tango:" | "taco:"

    if(devname.substr(0, 6) == "tango:")
    {
        devname.erase(0, 6);
    }
    else
    {
        if(devname.substr(0, 5) == "taco:")
        {
            devname.erase(0, 5);
        }
    }

    // check instance - "//instance/"

    if(devname.substr(0, 2) == "//")
    {
        index = devname.find('/', (std::string::size_type) 2);
        if(index == 0 || index == std::string::npos)
        {
            return false;
        }
        devname.erase(0, index + 1);
    }

    // check name conforms to "D/F/M"

    index = devname.find('/');
    index2 = devname.find('/', index + 1);

    if(index == 0 || index == std::string::npos || index2 == std::string::npos || index2 - index <= 0 ||
       devname.length() - index2 <= 0)
    {
        return false;
    }

    device_name_str = devname;

    DEBUG_STREAM << "DataBase::check_device_name(): device_name out " << device_name_str << std::endl;

    return true;
}

//+------------------------------------------------------------------
/**
 *	method:	DataBase::init_timing_stats
 *
 *	description:	method to initialise the timing statistics variables
 *	Returns void
 *
 * @param	none
 * @return	void
 *
 */
//+------------------------------------------------------------------
void DataBase::init_timing_stats()
{
    TimingStatsStruct new_timing_stats = {0.0, 0.0, 0.0, 0.0, 0.0};

    timing_stats_mutex.lock();

    timing_stats_map["DbImportDevice"] = new TimingStatsStruct;
    timing_stats_map["DbExportDevice"] = new TimingStatsStruct;
    timing_stats_map["DbGetHostServerList"] = new TimingStatsStruct;
    timing_stats_map["DbGetHostList"] = new TimingStatsStruct;
    timing_stats_map["DbGetServerList"] = new TimingStatsStruct;
    timing_stats_map["DbGetDevicePropertyList"] = new TimingStatsStruct;
    timing_stats_map["DbGetClassPropertyList"] = new TimingStatsStruct;
    timing_stats_map["DbGetDeviceMemberList"] = new TimingStatsStruct;
    timing_stats_map["DbGetDeviceFamilyList"] = new TimingStatsStruct;
    timing_stats_map["DbGetDeviceDomainList"] = new TimingStatsStruct;
    timing_stats_map["DbGetDeviceProperty"] = new TimingStatsStruct;
    timing_stats_map["DbPutDeviceProperty"] = new TimingStatsStruct;
    timing_stats_map["DbDeleteDeviceProperty"] = new TimingStatsStruct;
    timing_stats_map["DbInfo"] = new TimingStatsStruct;
    timing_stats_map["DbGetDeviceClassList"] = new TimingStatsStruct;
    timing_stats_map["DbGetDeviceAttributeProperty"] = new TimingStatsStruct;
    timing_stats_map["DbPutDeviceAttributeProperty"] = new TimingStatsStruct;
    timing_stats_map["DbGetDeviceAttributeProperty2"] = new TimingStatsStruct;
    timing_stats_map["DbPutDeviceAttributeProperty2"] = new TimingStatsStruct;
    timing_stats_map["DbUnExportServer"] = new TimingStatsStruct;
    timing_stats_map["DbGetDeviceExportedList"] = new TimingStatsStruct;
    timing_stats_map["DbExportEvent"] = new TimingStatsStruct;
    timing_stats_map["DbImportEvent"] = new TimingStatsStruct;
    timing_stats_map["DbGetDataForServerCache"] = new TimingStatsStruct;
    timing_stats_map["DbPutClassProperty"] = new TimingStatsStruct;
    timing_stats_map["DbMySqlSelect"] = new TimingStatsStruct;
    timing_stats_map["DbGetDevicePipeProperty"] = new TimingStatsStruct;
    timing_stats_map["DbPutDevicePipeProperty"] = new TimingStatsStruct;

    timing_stats_size = timing_stats_map.size();
    timing_stats_average = new double[timing_stats_size];
    timing_stats_minimum = new double[timing_stats_size];
    timing_stats_maximum = new double[timing_stats_size];
    timing_stats_calls = new double[timing_stats_size];
    timing_stats_index = new Tango::DevString[timing_stats_size];

    //	now loop over map and initialise remaining variables
    std::map<std::string, TimingStatsStruct *>::iterator iter;
    int i = 0;

    for(iter = timing_stats_map.begin(); iter != timing_stats_map.end(); iter++)
    {
        *(iter->second) = new_timing_stats;
        timing_stats_index[i] = CORBA::string_dup(iter->first.c_str());
        i++;
    }

    timing_stats_mutex.unlock();
}

//+----------------------------------------------------------------------------
//
// method : 		DataBase::check_history_tables()
//
// description : 	Return history id
//
//-----------------------------------------------------------------------------
void DataBase::check_history_tables()
{
    TangoSys_MemStream sql_query_stream;
    MYSQL_RES *result;

    INFO_STREAM << "DataBase::check_history_tables(): entering" << std::endl;

    sql_query_stream.str("");
    sql_query_stream << "SELECT count(*) FROM property_device_hist";
    DEBUG_STREAM << "DataBase::check_history_tables(): sql_query " << sql_query_stream.str() << std::endl;

    DatabaseConnectionHandle dch(dcp);
    result = query(sql_query_stream.str(), "check_history_tables()", dch);
    mysql_free_result(result);
}

//+----------------------------------------------------------------------------
//
// method : 		DataBase::get_id()
//
// description : 	Return history id
//
//-----------------------------------------------------------------------------

Tango::DevULong64 DataBase::get_id(const char *name, DatabaseConnectionHandle &dch)
{
    TangoSys_MemStream sql_query_stream;

    sql_query_stream.str("");
    sql_query_stream << "UPDATE " << name << "_history_id SET id=LAST_INSERT_ID(id+1)";
    std::string sql_query = sql_query_stream.str();

    simple_query(sql_query, "get_id()", dch);

    my_ulonglong val = mysql_insert_id(dch.db());
    if(val == 0)
    {
        TangoSys_OMemStream o;

        o << "Failed to get history id : " << name;

        Tango::Except::throw_exception(DB_SQLError, o.str(), "DataBase::get_id()");
    }

    return (Tango::DevULong64) val;
}

//+----------------------------------------------------------------------------
//
// method : 		DataBase::simple_query()
//
// description : 	Execute a SQL query , ignore the result.
//
//               	This method internally retries and should be always preferred compared to a plain
//               	mysql_real_query(). As it uses the passed database from the connection handle it can
//               	also be used then later querying the results.
//
//-----------------------------------------------------------------------------
void DataBase::simple_query(std::string sql_query, const char *method, DatabaseConnectionHandle &dch)
{
    //
    // Call MySQL and retry if the database is busy
    //
    const unsigned int retry_count = 10;                // Number of retries
    auto retry_delay_ms = std::chrono::milliseconds(8); // Initial delay in ms.

    for(unsigned int attempt = 0; attempt < retry_count; attempt++)
    {
        auto qresult = mysql_real_query(dch.db(), sql_query.c_str(), sql_query.length());
        if(qresult == 0)
        {
            return;
        }

        WARN_STREAM << "DataBase::" << method << " failed to query TANGO database:" << std::endl;
        WARN_STREAM << "  query = " << sql_query << std::endl;
        WARN_STREAM << " (SQL error=" << mysql_error(dch.db()) << ")" << std::endl;

        DEBUG_STREAM << "Will retry: " << sql_query << " attempt: " << attempt << std::endl;

        std::this_thread::sleep_for(retry_delay_ms);
        retry_delay_ms *= get_retry_waiting_factor();
    }

    TangoSys_OMemStream o;
    TangoSys_OMemStream o2;

    o << "Failed to query1 TANGO database (error=" << mysql_error(dch.db()) << ")";
    o << "\n.The query was: " << sql_query << std::ends;
    o2 << "DataBase::" << method << std::ends;

    Tango::Except::throw_exception(DB_SQLError, o.str(), o2.str());
}

//+----------------------------------------------------------------------------
//
// method : 		DataBase::query()
//
// description : 	Execute a SQL query and return the result.
//
//-----------------------------------------------------------------------------
MYSQL_RES *DataBase::query(std::string sql_query, const char *method, DatabaseConnectionHandle &dch)
{
    MYSQL_RES *result;

    simple_query(sql_query, method, dch);

    if((result = mysql_store_result(dch.db())) == nullptr)
    {
        TangoSys_OMemStream o;
        TangoSys_OMemStream o2;

        WARN_STREAM << "DataBase:: " << method << " : mysql_store_result() failed  (error=" << mysql_error(dch.db())
                    << ")" << std::endl;

        o << "mysql_store_result() failed (error=" << mysql_error(dch.db()) << ")";
        o2 << "DataBase::" << method;

        Tango::Except::throw_exception(DB_SQLError, o.str(), o2.str());
    }

    return result;
}

//+------------------------------------------------------------------
/**
 *	method:	purge_property()
 *
 *	description:	purge a property history table.
 *
 */
//+------------------------------------------------------------------
void DataBase::purge_property(
    const char *table, const char *field, const char *object, const char *name, DatabaseConnectionHandle &dch)
{
    TangoSys_MemStream sql_query;
    MYSQL_RES *result;
    MYSQL_ROW row2;

    sql_query.str("");
    sql_query << "SELECT DISTINCT id,date FROM " << table << " WHERE " << field << "=\"" << object << "\" AND name=\""
              << name << "\" ORDER by date";

    result = query(sql_query.str(), "purge_property()", dch);
    int nb_item = mysql_num_rows(result);

    if(nb_item > historyDepth)
    {
        // Purge
        int toDelete = nb_item - historyDepth;
        for(int j = 0; j < toDelete; j++)
        {
            row2 = mysql_fetch_row(result);
            sql_query.str("");
            sql_query << "DELETE FROM " << table << " WHERE id='" << row2[0] << "'";
            simple_query(sql_query.str(), "purge_property()", dch);
        }
    }

    mysql_free_result(result);
}

//+------------------------------------------------------------------
/**
 *	method:	purge_attribute_property()
 *
 *	description:	purge an attribute property history table.
 *
 */
//+------------------------------------------------------------------
void DataBase::purge_att_property(const char *table,
                                  const char *field,
                                  const char *object,
                                  const char *attribute,
                                  const char *name,
                                  DatabaseConnectionHandle &dch)
{
    TangoSys_MemStream sql_query;
    MYSQL_RES *result;
    MYSQL_ROW row2;

    // TANGO_LOG << "purge_att_property(" << object << "," << attribute << "," << name << ")" << std::endl;

    sql_query.str("");
    sql_query << "SELECT DISTINCT id,date FROM " << table << " WHERE " << field << "=\"" << object << "\" AND name=\""
              << name << "\" AND attribute=\"" << attribute << "\" ORDER by date";

    result = query(sql_query.str(), "purge_att_property()", dch);
    int nb_item = mysql_num_rows(result);

    if(nb_item > historyDepth)
    {
        // Purge
        int toDelete = nb_item - historyDepth;
        for(int j = 0; j < toDelete; j++)
        {
            row2 = mysql_fetch_row(result);
            sql_query.str("");
            sql_query << "DELETE FROM " << table << " WHERE id='" << row2[0] << "'";
            simple_query(sql_query.str(), "purge_att_property()", dch);
        }
    }
    mysql_free_result(result);
}

//+------------------------------------------------------------------
/**
 *	method:	purge_pipe_property()
 *
 *	description:	purge an pipe property history table.
 *
 */
//+------------------------------------------------------------------
void DataBase::purge_pipe_property(const char *table,
                                   const char *field,
                                   const char *object,
                                   const char *pipe,
                                   const char *name,
                                   DatabaseConnectionHandle &dch)
{
    TangoSys_MemStream sql_query;
    MYSQL_RES *result;
    MYSQL_ROW row2;

    // TANGO_LOG << "purge_pipe_property(" << object << "," << pipe << "," << name << ")" << std::endl;

    sql_query.str("");
    sql_query << "SELECT DISTINCT id,date FROM " << table << " WHERE " << field << "=\"" << object << "\" AND name=\""
              << name << "\" AND pipe=\"" << pipe << "\" ORDER by date";

    result = query(sql_query.str(), "purge_pipe_property()", dch);
    int nb_item = mysql_num_rows(result);

    if(nb_item > historyDepth)
    {
        // Purge
        int toDelete = nb_item - historyDepth;
        for(int j = 0; j < toDelete; j++)
        {
            row2 = mysql_fetch_row(result);
            sql_query.str("");
            sql_query << "DELETE FROM " << table << " WHERE id='" << row2[0] << "'";
            simple_query(sql_query.str(), "purge_pipe_property()", dch);
        }
    }
    mysql_free_result(result);
}

//+------------------------------------------------------------------
/**
 *	method:	host_port_from_ior()
 *
 *	description:	Get host and port from a device IOR
 *
 */
//+------------------------------------------------------------------

bool DataBase::host_port_from_ior(const char *iorstr, std::string &h_p)
{
    size_t s = (iorstr ? strlen(iorstr) : 0);

    if(s < 4)
    {
        return false;
    }

    const char *p = iorstr;
    if(p[0] != 'I' || p[1] != 'O' || p[2] != 'R' || p[3] != ':')
    {
        return false;
    }

    s = (s - 4) / 2; // how many octets are there in the string
    p += 4;

    cdrMemoryStream buf((CORBA::ULong) s, 0);

    for(int i = 0; i < (int) s; i++)
    {
        int j = i * 2;
        CORBA::Octet v;

        if(p[j] >= '0' && p[j] <= '9')
        {
            v = ((p[j] - '0') << 4);
        }
        else if(p[j] >= 'a' && p[j] <= 'f')
        {
            v = ((p[j] - 'a' + 10) << 4);
        }
        else if(p[j] >= 'A' && p[j] <= 'F')
        {
            v = ((p[j] - 'A' + 10) << 4);
        }
        else
        {
            return false;
        }

        if(p[j + 1] >= '0' && p[j + 1] <= '9')
        {
            v += (p[j + 1] - '0');
        }
        else if(p[j + 1] >= 'a' && p[j + 1] <= 'f')
        {
            v += (p[j + 1] - 'a' + 10);
        }
        else if(p[j + 1] >= 'A' && p[j + 1] <= 'F')
        {
            v += (p[j + 1] - 'A' + 10);
        }
        else
        {
            return false;
        }

        buf.marshalOctet(v);
    }

    buf.rewindInputPtr();
    CORBA::Boolean b = buf.unmarshalBoolean();
    buf.setByteSwapFlag(b);

    IOP::IOR ior;

    ior.type_id = IOP::IOR::unmarshaltype_id(buf);
    ior.profiles <<= buf;

    if(ior.profiles.length() == 0 && strlen(ior.type_id) == 0)
    {
        return false;
    }
    else
    {
        for(unsigned long count = 0; count < ior.profiles.length(); count++)
        {
            if(ior.profiles[count].tag == IOP::TAG_INTERNET_IOP)
            {
                IIOP::ProfileBody pBody;
                IIOP::unmarshalProfile(ior.profiles[count], pBody);

                //
                // Three possible cases for host name:
                // 1 - The host is stored in IOR as IP numbers
                // 2 - The host name is stored in IOR as the canonical host name
                // 3 - The FQDN is stored in IOR
                // We allways try to get the host name as the FQDN
                //

                ho = pBody.address.host.in();
                bool host_is_name = false;

                std::string::size_type pos = ho.find('.');
                if(pos == std::string::npos)
                {
                    host_is_name = true;
                }
                else
                {
                    for(unsigned int loop = 0; loop < pos; ++loop)
                    {
                        if(isdigit((int) ho[loop]) == 0)
                        {
                            host_is_name = true;
                            break;
                        }
                    }
                }

                if(host_is_name == false)
                {
                    struct sockaddr_in sock;
                    char service[20];

                    sock.sin_family = AF_INET;
                    int res;
#ifdef _TG_WINDOWS_
                    sock.sin_addr.s_addr = inet_addr(ho.c_str());
                    if(sock.sin_addr.s_addr != INADDR_NONE)
#else
                    res = inet_pton(AF_INET, ho.c_str(), &(sock.sin_addr.s_addr));
                    if(res == 1)
#endif
                    {
                        res = getnameinfo((const struct sockaddr *) &sock,
                                          sizeof(sock),
                                          ho_name,
                                          sizeof(ho_name),
                                          service,
                                          sizeof(service),
                                          0);
                        if(res == 0)
                        {
                            h_p = ho_name;
                            h_p = h_p + ':';
                        }
                        else
                        {
                            h_p = ho + ':';
                        }
                    }
                    else
                    {
                        h_p = ho + ':';
                    }
                }
                else
                {
                    if(pos == std::string::npos)
                    {
                        Tango::DeviceProxy::get_fqdn(ho);
                    }

                    h_p = ho + ':';
                }

                //
                // Add port number
                //

                std::stringstream ss;
                ss << pBody.address.port;
                h_p = h_p + ss.str();

                break;
            }
        }
    }

    return true;
}

//+------------------------------------------------------------------
/**
 *	method:	AutoLock class ctor and dtor
 *
 *	description:	AutoLock is a small helper class which get a MySQL
 *	              connection from the pool and manages the DB transaction.
 */
//+------------------------------------------------------------------

AutoLock::AutoLock(DataBase *db) :
    the_db(db),
    m_dch(the_db->dcp)
{
    const std::string sql_query{"SET autocommit=0;"}; // or START TRANSACTION
    the_db->simple_query(sql_query, "AutoLock", m_dch);
}

AutoLock::~AutoLock()
{
    const std::string sql_query{"SET autocommit=1;"}; // or COMMIT
    the_db->simple_query(sql_query, "~AutoLock", m_dch);
}

/// Return a random floating point number between 1 and 2
double DataBase::get_retry_waiting_factor()
{
    return distrib(random_engine);
}

void DataBase::db_delete_device_impl(Tango::DevString argin, DatabaseConnectionHandle &dch)
{
    Tango::DevString device = argin;
    TangoSys_MemStream sql_query_stream;
    std::string tmp_device;

    INFO_STREAM << "DataBase::db_delete_device(): delete " << device << " from database" << std::endl;

    // first check the device name

    tmp_device = device;
    if(!check_device_name(tmp_device))
    {
        WARN_STREAM << "DataBase::db_delete_device(): device name  " << device << " incorrect ";
        WARN_STREAM << std::endl;
        Tango::Except::throw_exception(
            DB_IncorrectDeviceName, "failed to delete device, device name incorrect", "DataBase::db_delete_device()");
    }

    // replace database wildcards (% and _)

    std::string tmp_wildcard = replace_wildcard(tmp_device.c_str());

    // then delete the device from the device table

    sql_query_stream << "DELETE FROM device WHERE name LIKE \"" << tmp_wildcard << "\"";
    DEBUG_STREAM << "DataBase::db_delete_device(): sql_query " << sql_query_stream.str() << std::endl;
    simple_query(sql_query_stream.str(), "db_delete_device()", dch);

    // then delete device from the property_device table

    sql_query_stream.str("");
    sql_query_stream << "DELETE FROM property_device WHERE device LIKE \"" << tmp_wildcard << "\"";
    DEBUG_STREAM << "DataBase::db_delete_device(): sql_query " << sql_query_stream.str() << std::endl;
    simple_query(sql_query_stream.str(), "db_delete_device()", dch);

    // then delete device from the property_attribute_device table

    sql_query_stream.str("");
    sql_query_stream << "DELETE FROM property_attribute_device WHERE device LIKE \"" << tmp_wildcard << "\"";
    DEBUG_STREAM << "DataBase::db_delete_device(): sql_query " << sql_query_stream.str() << std::endl;
    simple_query(sql_query_stream.str(), "db_delete_device()", dch);

    // then delete device from the property_pipe_device table

    sql_query_stream.str("");
    sql_query_stream << "DELETE FROM property_pipe_device WHERE device LIKE \"" << tmp_wildcard << "\"";
    DEBUG_STREAM << "DataBase::db_delete_device(): sql_query " << sql_query_stream.str() << std::endl;
    simple_query(sql_query_stream.str(), "db_delete_device()", dch);

    // then delete device from attribute_alias table

    sql_query_stream.str("");
    sql_query_stream << "DELETE FROM attribute_alias WHERE device LIKE \"" << tmp_wildcard << "\"";
    DEBUG_STREAM << "DataBase::db_delete_device(): sql_query " << sql_query_stream.str() << std::endl;
    simple_query(sql_query_stream.str(), "db_delete_device()", dch);
}

} // namespace DataBase_ns
