znajomy mnie prosił, abym mu zrobił mały programik (zaliczenie) przy tej okazji, aby się czegoś nauczyć chciałem napisać bibliotekę do MySQL-a

coś napisałem jakoś to działa, ale jako, że do tej pory głównie PHP (przez co wiele na rzeczy nie musiałem zwracać uwagi) to nie wiem czy to jest napisane dobrze, poprawnie, czy takie coś może być i dodatkowo mam parę pytań

klasa jest Singletonem (mam nadzieję, że dobrze go w ogóle zastosowałem), chyba dobrze, ale w związku z tym pojawiają się pytania
1 - obsługa wielowątkowości, jak to wykonać, przykład gdy wątki próbują robić coś równocześnie (jak temu zapobiec, lub jak to obsłużyć) np w przypadku obiektu klasy RECORDSET jeden cos tam jeszcze myka pobiera, się wcina i przed pobraniem następuje zamkniecie (close()) obiektu recordset
2 - jak pobrać ilość zmodyfikowanych wierszy (np poprzez UPDATE, DELETE)
3 - o co jeszcze można by klasę rozszerzyć, co zmienić, co wyrzucić

chciałbym aby ta klasa była w miarę do wykorzystania w różnych sytuacjach i jakoś uniwersalna i dlatego liczę na jakieś uwagi, pomysły, wnioski itp itd i z góry za nie dziękuję, bo wiem, ze kodu jest dużo

plik nagłówkowy

#pragma once
#include <afxdb.h>
#include "stdafx.h"
class dbMySQLOperation
{
private:
    //wskaźnik na instancje obiektu - STATYCZNY
    static dbMySQLOperation *dbMySQLOperationInstance;
    //wskaźnik na CDatabase - STATYCZNY
    static CDatabase *m_pDBConn;
    //wskaźnik na CRecordset - STATYCZNY
    static CRecordset *m_pRecorset;
    //zmienne przechowujące kod błedu i tekst błędu
    CString m_strErrorInfo;
    UINT m_uiErrorCode;
    
    //konstruktor prywatny
    dbMySQLOperation(void);
    
    //metoda łącząca z bazą danych
    BOOL makeConection(CString,BOOL bNoODBC=FALSE) const;
    
    //metoda sprawdzająca czy RECORDSET jest otwarty i ZAMYKAJĄCA GO
    void checkRecordsetOpen();
    
    
public:
    //destruktor
    ~dbMySQLOperation(void);
    //metoda zwarcająca instancję klasy
    static dbMySQLOperation* getInstance();
    //metoda łącząca z MySQL (dokładniej tworzy connection string, znaleziony na stronie MySQL.com
    BOOL setConnection(CString strServer,CString strDataBase, CString strUser,CString strPass, CString strCharset=_T("UTF8"));
    //metoda łącząca z MySQL (dokładniej tworzy connection string, znaleziony na stronie MySQL.com), ale z opcją noOdbcBialog
    BOOL setConnectionNoODBCDlg(CString strServer, CString strUser, CString strPass, CString strDB);
    
    //metoda do SELECTÓW - zwraca wskaźnik do recordset 
    CRecordset* selectRecord(CString sql,UINT uiOpenType,UINT uiOpenOption);
    //metoda do wykonania zapytania wywołuje m_pDBConn->ExecuteSQL 
    void executeQuery(CString sqlQuery);
    /*metoda wywołująca zapytanie modyfikujące (INSERT,UPDATE,DELETE), 
        jeśli jest INSERT zwraca LAST_INSERT_ID
        jeśłi jest UPDATE,DELETE chcę aby zwracało ILOŚ ZMODYFIKOWANYCH REKORDÓW, ALE NIE WIEM JAK TO ZROBI
    */
    UINT insertUpdateDelete(CString sqlQuery, CString strTable=_T(""),BOOL iInsert=FALSE);
    
    //pobieranie informacji tekstowej o błedzie
    CString getErrorInfo() const;
    //pobieranie kodu błędu
    UINT getErrorCode() const;
    //sprawdzanie czy możliwa jest transakcja 
    BOOL transEnabled() const;
    //rozpoczęcie transakcji
    BOOL beginTrans() const;
    //zakończenie transakcji - jeśli warunek jest prawda to COMMIT jeśli fałszem to ROLLBACK
    BOOL endTrans(BOOL bCondition) const;
    //sprawdzenie czy zakończyć transakcję - jeśli warunek jest FAŁSZEM jest ROLLBACK 
    BOOL checkRollback(BOOL bCondition) const;
    //sprawdzenie czy baza danych jest otwarta (połączenie jest otwarte)
    BOOL checkDBOpen() const;
    //pobieranie jednej wartości z zapytnia (najczęsciej pewnie będzie to COUNT) znajdującej się na pierwszym miejscy w zapytaniu SELECT
    void getScalarValue(CString strQuery, CDBVariant &varDB, UINT uiFieldtype=0);
};

plik CPP

#include "StdAfx.h"
#include "dbMySQLOperation.h"

//inicjalizacja zmiennych statycznych
dbMySQLOperation *dbMySQLOperation::dbMySQLOperationInstance = NULL;
CDatabase *dbMySQLOperation::m_pDBConn=NULL;
CRecordset *dbMySQLOperation::m_pRecorset=NULL;

//konstruktor
dbMySQLOperation::dbMySQLOperation(void)
{
  
}

//destruktor - usuwanie wskaźników, zamykanie połączenia z bazą danych
dbMySQLOperation::~dbMySQLOperation(void)
{
    if(dbMySQLOperationInstance!=NULL)
        delete(dbMySQLOperationInstance);
    if(m_pDBConn!=NULL)
         delete(m_pDBConn);
    if(m_pRecorset!=NULL)
        delete(m_pRecorset);
    m_pRecorset=NULL;         
    m_pDBConn = NULL;
    dbMySQLOperationInstance = NULL;
    m_pDBConn->Close();
}
/*pobiernaie instancji klasy
równocześnie tworzony jest obiekt klasy CDatabase
*/
dbMySQLOperation* dbMySQLOperation::getInstance()
{
    if(dbMySQLOperationInstance==NULL)
    {
        dbMySQLOperationInstance = new dbMySQLOperation();
        m_pDBConn = new CDatabase();
    }
    return dbMySQLOperationInstance;
}
/*
    tworzenie stringu do połączenia z bazą danych
*/
BOOL dbMySQLOperation::setConnection(CString strServer,CString strDataBase, CString strUser,CString strPass, CString strCharset)
{
    CString strConn;
    strConn.Format(_T("Driver={MySQL ODBC 3.51 Driver};Server=%s;charset=%s;Database=%s;User=%s; Password=%s;Option=3;"),(LPCTSTR)strServer,(LPCTSTR)strCharset,(LPCTSTR)strDataBase,(LPCTSTR)strUser,(LPCTSTR)strPass);
    
    return makeConection(strConn);  
    
}
/*
    tworzenie stringu do połączenia z bazą danych dla OpenEx i dla noOdbcDialog
*/

BOOL dbMySQLOperation::setConnectionNoODBCDlg(CString strServer, CString strUser, CString strPass, CString strDB)
{
    CString strConn;
    strConn.Format(_T("DRIVER={MySQL ODBC 3.51 Driver};Server=%s;Uid=%s;Pwd=%s;Database=%s;Port=3306"),(LPCTSTR)strServer,(LPCTSTR)strUser,(LPCTSTR)strPass,(LPCTSTR)strDB);
    return makeConection(strConn,TRUE);
}

/*
    faktyczne połączenie z bazą danych
    inicjalizacja obiektu CRecordset
*/
BOOL dbMySQLOperation::makeConection(CString strConnectionString, BOOL bNoODBCDlg) const
{
    BOOL bRet=FALSE;
    //jeśłi połączenie jest otwarte to nic nie rób
    if(checkDBOpen())
        return bRet;
    if(bNoODBCDlg)
      bRet=m_pDBConn->OpenEx(strConnectionString,CDatabase::noOdbcDialog);
    else
      bRet=m_pDBConn->Open(_T("ODBC"), FALSE, FALSE,strConnectionString);
    if(bRet)
        m_pRecorset = new CRecordset(m_pDBConn );
    return bRet; 
}
/*
    metoda do pobierania rekordów
    argumenty - ZAPYTANIE, TYP OTWRACIA , OPCJA OTWARCIA
*/
CRecordset* dbMySQLOperation::selectRecord(CString sql,UINT uiOpenType,UINT uiOpenOption)
{
    ASSERT(uiOpenType<4);
    ASSERT(m_pRecorset!=NULL);
    checkRecordsetOpen();
    try
    {
        m_pRecorset->Open(uiOpenType,sql,uiOpenOption);
        m_strErrorInfo="";
        m_uiErrorCode=0;
    }
    catch(CDBException* e)
    {
       m_strErrorInfo = e->m_strError;
       m_uiErrorCode=e->m_nRetCode;
    }
   //zwraca wskaźnik do recordset
    return m_pRecorset;
} 

/*metoda do wykonywania zapyań INSERT UPDATE DELETE 
    
*/
UINT dbMySQLOperation::insertUpdateDelete(CString sqlQuery, CString strTable,BOOL iInsert)
{
   ASSERT(m_pRecorset!=NULL);
   UINT uiRetVal=0;
   checkRecordsetOpen();
   try
   {    sqlQuery.Trim();
        executeQuery(sqlQuery);
        //jeśli insert to pobierz LAST INSERT ID
        if(iInsert)
        {
           CDBVariant varDB; 
           CString strLastID;
           strLastID.Format(_T("SELECT LAST_INSERT_ID() AS ID FROM %s LIMIT 1"),strTable);
           getScalarValue(strLastID,varDB,SQL_C_SLONG);
           uiRetVal = varDB.m_iVal;
        }   
        /*
            tu chciałbym aby było 
            ELSE 
            {
                ZWRÓC ILOŚC ZMODYFIKOWANYCH REKORDÓW
            }
        */
        m_strErrorInfo = "";
        m_uiErrorCode=0;
   }
   catch(CDBException* e)
   {
       m_strErrorInfo = e->m_strError;
       m_uiErrorCode=e->m_nRetCode;
   } 
   return uiRetVal;
}

/*wykonaj zapytanie za pomocą  m_pDBConn->ExecuteSQL*/

 void dbMySQLOperation::executeQuery(CString sqlQuery)
 {
    ASSERT(m_pDBConn!=NULL);
    try 
    {
       m_pDBConn->ExecuteSQL((LPCTSTR)sqlQuery);
       m_strErrorInfo="";
       m_uiErrorCode=0;
    }
    catch(CDBException* e)
    {
        m_strErrorInfo = e->m_strError;
        m_uiErrorCode=e->m_nRetCode;
    }
   
 }
 //pobierz KOD BŁEDU
 UINT dbMySQLOperation::getErrorCode() const
 {
    return m_uiErrorCode;
 }
 //pobierz TEKST BŁEDU
 CString dbMySQLOperation::getErrorInfo() const
 {
    return m_strErrorInfo;
 }
 //rozpocznij transakcję
 BOOL dbMySQLOperation::beginTrans() const
 {
    /*jeśli transkacje jest dostępna sprój rozpocząć*/
    if(transEnabled())
        return m_pDBConn->BeginTrans();
    return FALSE;    
 }
 /*
    zakończenie transakcji - jeśli warunek to TRUE zrób COMMIT jeśli nie zrób ROLLBACK
 */
 BOOL dbMySQLOperation::endTrans(BOOL bCondition) const
 {
    if(bCondition)
        return m_pDBConn->CommitTrans();
    else
        return m_pDBConn->Rollback();
 }
 /*
   sprawdź czy kontynuwać TRANSAKCJĘ 
   true oznacza, że już wystąpiło coś co przerwało transakcję
 */
 BOOL dbMySQLOperation::checkRollback(BOOL bCondition) const
 {
    
    if(!bCondition)
        return m_pDBConn->Rollback();
    return FALSE;    
 }
 //sprawdza czy można robić transakcję
 BOOL dbMySQLOperation::transEnabled() const
 {
    return m_pDBConn->CanTransact();
 }   
 //sprawdza czy połączenie z bazą jest otwarte
 BOOL dbMySQLOperation::checkDBOpen() const
 {
    return m_pDBConn->IsOpen();
 }
 //sprawdza pobiera pojedyńczą wartość z zapytania
 /*
    wartość tą można uszczegółowić podając typ pola do jakiego ma być przekierowane, wartości to np
    SQL_C_CHAR, SQL_C_SLONG itd
    
    czy jest sens rozszerzyć o możliwość podania numeru pola, którego ma to dotyczyć?
    
 */
   void dbMySQLOperation::getScalarValue(CString strQuery,CDBVariant &varDB,UINT uiFieldtype)
  {
    checkRecordsetOpen();
    m_pRecorset->Open(m_pRecorset->snapshot, strQuery, m_pRecorset->readOnly);
    if( !m_pRecorset->IsBOF() && !m_pRecorset->IsEOF() )
    {
         UINT uiFieldPos = 0;
         m_pRecorset->GetFieldValue(uiFieldPos,varDB,uiFieldtype);
    }
  }
  //sprawdza czy recorset jest otwarty i zamyka go jeśli jest otwarty
  /*
    chodzi o to, aby na zewnątrz klasy tego nie robić
    ktoś tam w dialogu pobiera wartości uzupełnia Listę czy Combo
    a potem coś dodaje, lub pobiera coś nowego
    i przed kolejnym otworzeniem recorseta jest sprawdzane czy ten nie jest otwarty i następuje zamkniecie go
  */
  void dbMySQLOperation::checkRecordsetOpen()
  {
    if(m_pRecorset->IsOpen())
        m_pRecorset->Close(); 
  }