2017-10-26 10:28:19 +00:00
|
|
|
|
#include "xvtdb.h"
|
|
|
|
|
#include <sqlapi.h>
|
|
|
|
|
|
|
|
|
|
#define _CON(a) ((SAConnection *)a)
|
|
|
|
|
#define _RCS(a) ((SACommand *)a)
|
|
|
|
|
#define _ERR(a) ((SAException *)a)
|
|
|
|
|
|
|
|
|
|
// Funzione per la connessione, utilizzando un DBMS devo controllare sempre
|
|
|
|
|
int xvt_create_connection(P_CONN_VOID con, const char* db, const char* user, const char* pass, TT_driver tipoDb)
|
|
|
|
|
{
|
|
|
|
|
if(con != NULL)
|
|
|
|
|
{
|
|
|
|
|
// Se <20> gi<67> connesso lo scollego
|
|
|
|
|
if(_CON(con)->isConnected())
|
|
|
|
|
_CON(con)->Disconnect();
|
|
|
|
|
|
|
|
|
|
SAString dbAddress = db;
|
|
|
|
|
SAString usr = user;
|
|
|
|
|
SAString psw = pass;
|
|
|
|
|
SAClient_t dbDriver = (SAClient_t)tipoDb;
|
|
|
|
|
if (dbAddress.IsEmpty() || usr.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
return NOT_INITIALIZED;
|
|
|
|
|
}
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// Mi collego
|
|
|
|
|
_CON(con)->Connect(dbAddress, usr, psw, dbDriver);
|
|
|
|
|
// Imposto che non si possono vedere i record non committati
|
|
|
|
|
_CON(con)->setIsolationLevel(SA_ReadCommitted);
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
return x.ErrNativeCode();
|
|
|
|
|
}
|
|
|
|
|
return NOERR;
|
|
|
|
|
}
|
|
|
|
|
return NOT_INITIALIZED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
|
* TXvt_recordset *
|
|
|
|
|
* Classe per esecuzioni di query temporanee (wrapper semplice per SACommand) *
|
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
|
|
TXvt_recordset::TXvt_recordset()
|
|
|
|
|
{
|
|
|
|
|
_con = new SAConnection;
|
|
|
|
|
_recset = new SACommand;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_RCS(_recset)->setConnection(_CON(_con));
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TXvt_recordset::TXvt_recordset(const char* db, const char* user, const char* pass, TT_driver tipoDb, const char * query, const bool ex, const bool freezed)
|
|
|
|
|
{
|
|
|
|
|
_con = new SAConnection;
|
|
|
|
|
if(xvt_create_connection(_con, db, user, pass, tipoDb) == NOERR)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_recset = new SACommand;
|
|
|
|
|
_RCS(_recset)->setConnection(_CON(_con));
|
|
|
|
|
// if (query[0] != '\0')
|
|
|
|
|
if (query && *query)
|
|
|
|
|
{
|
|
|
|
|
set(query);
|
|
|
|
|
if (ex)
|
|
|
|
|
{
|
|
|
|
|
exec();
|
|
|
|
|
// Terribile da vedere, ma ho fatto una funzione apposta e la voglio usare per Diana!
|
|
|
|
|
if(freezed)
|
|
|
|
|
freeze();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TXvt_recordset::TXvt_recordset(P_CONN_VOID c, const char * query, bool ex)
|
|
|
|
|
{
|
|
|
|
|
_toDisconnect = true;
|
|
|
|
|
//_con = c;
|
|
|
|
|
memcpy(_con, c, sizeof(c));
|
|
|
|
|
if (!_CON(_con)->isConnected())
|
|
|
|
|
bool tolla = true;
|
|
|
|
|
_recno = -1;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_recset = new SACommand;
|
|
|
|
|
_RCS(_recset)->setConnection(_CON(_con));
|
|
|
|
|
// if (query[0] != '\0')
|
|
|
|
|
if (query && *query)
|
|
|
|
|
{
|
|
|
|
|
set(query);
|
|
|
|
|
if (ex)
|
|
|
|
|
{
|
|
|
|
|
exec();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TXvt_recordset::~TXvt_recordset()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if(_toDisconnect && _CON(_con)->isConnected())
|
|
|
|
|
_CON(_con)->Disconnect();
|
|
|
|
|
if(_con != NULL)
|
|
|
|
|
delete _con;
|
|
|
|
|
if(_recset != NULL)
|
|
|
|
|
delete _recset;
|
|
|
|
|
}
|
|
|
|
|
catch(...){}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* PRIVATE FUNCTIONS **************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
const bool TXvt_recordset::checkPermission()
|
|
|
|
|
{
|
|
|
|
|
bool err = isFreezed();
|
|
|
|
|
if(err)
|
|
|
|
|
{
|
|
|
|
|
_codeError = ERROR_FREEZED;
|
|
|
|
|
_stringError = "Il recordset <20> bloccato";
|
|
|
|
|
}
|
|
|
|
|
return !err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* PUBLIC FUNCTIONS **************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
/**************************************************************************************************
|
|
|
|
|
* Gestione Connection *
|
|
|
|
|
**************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
int TXvt_recordset::connect(const char* db, const char* user, const char* pass, TT_driver tipoDb)
|
|
|
|
|
{
|
|
|
|
|
return xvt_create_connection(_con, db, user, pass, tipoDb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TXvt_recordset::disconnect()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_CON(_con)->Disconnect();
|
|
|
|
|
}
|
|
|
|
|
catch(SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TXvt_recordset::commit(bool autoRoll)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_CON(_con)->Commit();
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
if (autoRoll)
|
|
|
|
|
{
|
|
|
|
|
rollback();
|
|
|
|
|
}
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TXvt_recordset::rollback()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_CON(_con)->Rollback();
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TXvt_recordset::setClient(TT_driver client)
|
|
|
|
|
{
|
|
|
|
|
_CON(_con)->setClient((SAClient_t) client);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TXvt_recordset::setConOption(const char* opt)
|
|
|
|
|
{
|
|
|
|
|
_CON(_con)->setOption(opt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TXvt_recordset::setAutocommit(bool ac)
|
|
|
|
|
{
|
|
|
|
|
_CON(_con)->setAutoCommit(ac ? SA_AutoCommitOn : SA_AutoCommitOff);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TXvt_recordset::setVisibility(isoLvl vis)
|
|
|
|
|
{
|
|
|
|
|
/* La libreria attuale supporta diversi tipi di visibilit<69>, per mantenere una compatibilit<69> massima
|
|
|
|
|
* (evitando inutili seghe mentali) consiglio di usare i primi due, di default la classe imposta "Read committed"
|
|
|
|
|
* Tipi di isolazione:
|
|
|
|
|
* 0 -> Read uncommitted.
|
|
|
|
|
* 1 -> Read committed.
|
|
|
|
|
* 2 -> Repeatable read.
|
|
|
|
|
* 3 -> Serializable.
|
|
|
|
|
*/
|
|
|
|
|
_CON(_con)->setIsolationLevel((SAIsolationLevel_t)vis);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TXvt_recordset::isConnect()
|
|
|
|
|
{
|
|
|
|
|
return _CON(_con)->isConnected();
|
|
|
|
|
}
|
|
|
|
|
bool TXvt_recordset::isAlive()
|
|
|
|
|
{
|
|
|
|
|
return _CON(_con)->isAlive();
|
|
|
|
|
}
|
|
|
|
|
int TXvt_recordset::getVisibility()
|
|
|
|
|
{
|
|
|
|
|
return (isoLvl)_CON(_con)->IsolationLevel();
|
|
|
|
|
}
|
|
|
|
|
bool TXvt_recordset::getAutocommit()
|
|
|
|
|
{
|
|
|
|
|
return _CON(_con)->AutoCommit() == SA_AutoCommitOn;
|
|
|
|
|
}
|
|
|
|
|
const char* TXvt_recordset::getOption(const char* opt)
|
|
|
|
|
{
|
|
|
|
|
return _CON(_con)->Option(opt);
|
|
|
|
|
}
|
|
|
|
|
long TXvt_recordset::getClientV()
|
|
|
|
|
{
|
|
|
|
|
return _CON(_con)->ClientVersion();
|
|
|
|
|
}
|
|
|
|
|
const char* TXvt_recordset::getServerV()
|
|
|
|
|
{
|
|
|
|
|
return _CON(_con)->ServerVersionString();
|
|
|
|
|
}
|
|
|
|
|
long TXvt_recordset::getServerVN()
|
|
|
|
|
{
|
|
|
|
|
return _CON(_con)->ServerVersion();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**************************************************************************************************
|
|
|
|
|
* Gestione Recordset *
|
|
|
|
|
**************************************************************************************************/
|
|
|
|
|
bool TXvt_recordset::set(const char* query)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_RCS(_recset)->setCommandText(query);
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TXvt_recordset::exec(bool autoF)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_RCS(_recset)->Execute();
|
|
|
|
|
_recno = -1;
|
|
|
|
|
if (autoF)
|
|
|
|
|
{
|
|
|
|
|
next();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TXvt_recordset::next()
|
|
|
|
|
{
|
|
|
|
|
bool fetched = false;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if(_RCS(_recset)->FetchNext())
|
|
|
|
|
{
|
|
|
|
|
fetched = true;
|
|
|
|
|
_recno++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
}
|
|
|
|
|
return fetched;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TXvt_recordset::prev()
|
|
|
|
|
{
|
|
|
|
|
bool fetched = false;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (_RCS(_recset)->FetchPrior())
|
|
|
|
|
{
|
|
|
|
|
fetched = true;
|
|
|
|
|
_recno--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
}
|
|
|
|
|
return fetched;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TXvt_recordset::first()
|
|
|
|
|
{
|
|
|
|
|
bool fetched = false;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (_RCS(_recset)->FetchFirst())
|
|
|
|
|
{
|
|
|
|
|
fetched = true;
|
|
|
|
|
_recno = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
}
|
|
|
|
|
return fetched;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TXvt_recordset::last()
|
|
|
|
|
{
|
|
|
|
|
/* La vita sarebbe molto bella se potessi chiamare la funzione FetchLast(),
|
|
|
|
|
// siccome non so la posizione del record eseguo ciclicamente Next
|
|
|
|
|
// _RCS(_recset)->FetchLast();
|
|
|
|
|
while (Next())
|
|
|
|
|
{
|
|
|
|
|
_recno++;
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
bool fetched = false;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (_RCS(_recset)->FetchLast())
|
|
|
|
|
{
|
|
|
|
|
fetched = true;
|
|
|
|
|
_recno = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
}
|
|
|
|
|
return fetched;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TXvt_recordset::go(int newPos)
|
|
|
|
|
{
|
|
|
|
|
// Controllo che la nuova posizione non sia fuori dal limite inferiore (Non so quanto <20> grande il recordset)
|
|
|
|
|
bool result = newPos >= 0 ? true : false;
|
|
|
|
|
|
|
|
|
|
// Se la posizione <20> minore mi sposto indietro
|
|
|
|
|
while (newPos < _recno && result)
|
|
|
|
|
{
|
|
|
|
|
result = prev();
|
|
|
|
|
}
|
|
|
|
|
// Se la posizione <20> maggiore mi sposto in avanti
|
|
|
|
|
while (newPos > _recno && result)
|
|
|
|
|
{
|
|
|
|
|
result = next();
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
// Controllo finale per prevenire errori
|
|
|
|
|
if (newPos == _recno)
|
|
|
|
|
return true;
|
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
*/
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int TXvt_recordset::get_int(const char* field)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return static_cast<int>(_RCS(_recset)->Field(field).asLong());
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
short TXvt_recordset::get_short(const char* field)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return _RCS(_recset)->Field(field).asShort();
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
long TXvt_recordset::get_long(const char* field)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return _RCS(_recset)->Field(field).asLong();
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double TXvt_recordset::get_double(const char* field)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return _RCS(_recset)->Field(field).asDouble();
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TXvt_recordset::get_bool(const char* field)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return _RCS(_recset)->Field(field).asBool();
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
real TXvt_recordset::GetReal(const char * field)
|
|
|
|
|
{
|
|
|
|
|
return Get(field);
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
const char* TXvt_recordset::getDate(const char * field)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
SADateTime app = _RCS(_recset)->Field(field).asDateTime();
|
2017-11-02 14:08:41 +00:00
|
|
|
|
static char date[10];
|
2017-10-26 10:28:19 +00:00
|
|
|
|
sprintf_s(date, (size_t)10, "%d-%d-%d", app.GetDay(), app.GetMonth(), app.GetYear());
|
|
|
|
|
return date;
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char* TXvt_recordset::get(const char* field)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return static_cast<const char *>(_RCS(_recset)->Field(field).asString());
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char TXvt_recordset::get_char(const char* field)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return _RCS(_recset)->Field(field).asString()[0];
|
|
|
|
|
}
|
|
|
|
|
catch (SAException &x)
|
|
|
|
|
{
|
|
|
|
|
_codeError = x.ErrNativeCode();
|
|
|
|
|
_stringError = x.ErrMessage();
|
|
|
|
|
return '\0';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
long TXvt_recordset::getCodeError(bool erase)
|
|
|
|
|
{
|
|
|
|
|
long app = _codeError;
|
|
|
|
|
if (erase)
|
|
|
|
|
_codeError = NOERR;
|
|
|
|
|
return app;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char* TXvt_recordset::getStringError(bool erase)
|
|
|
|
|
{
|
|
|
|
|
const char* app = _stringError.c_str();
|
|
|
|
|
if (erase)
|
|
|
|
|
_stringError.erase();
|
|
|
|
|
return app;
|
|
|
|
|
}
|