21#include <boost/algorithm/string.hpp> 
   22#include <boost/locale.hpp> 
   24#include <nanodbc/nanodbc.h> 
   29#define UINT64 uint64_t 
   66nanodbc::string 
fromUTF8( 
const std::string& aString )
 
   68    return boost::locale::conv::utf_to_utf<nanodbc::string::value_type>( aString );
 
 
   77std::string 
toUTF8( 
const nanodbc::string& aString )
 
   79    return boost::locale::conv::utf_to_utf<char>( aString );
 
 
   84                                          const std::string& aUsername,
 
   85                                          const std::string& aPassword, 
int aTimeoutSeconds,
 
   89    m_dsn     = aDataSourceName;
 
  102                                          int aTimeoutSeconds, 
bool aConnectNow ) :
 
 
  124    m_cache = std::make_unique<DB_CACHE_TYPE>( 10, 1 );
 
 
  139    m_cache->SetMaxSize( 
static_cast<size_t>( aMaxSize ) );
 
  140    m_cache->SetMaxAge( 
static_cast<time_t
>( aMaxAge ) );
 
 
  156            m_conn = std::make_unique<nanodbc::connection>( dsn, user, pass, 
m_timeout );
 
  160            wxLogTrace( 
traceDatabase, wxT( 
"Creating connection with connection string" ) );
 
  164    catch( std::exception& e )
 
 
  183        wxLogTrace( 
traceDatabase, wxT( 
"Note: Disconnect() called without valid connection" ) );
 
  191    catch( std::exception& exc )
 
  193        wxLogTrace( 
traceDatabase, wxT( 
"Disconnect() error \"%s\" occured." ), exc.what() );
 
  197    return !
m_conn->connected();
 
 
  206    return m_conn->connected();
 
 
  211                                          const std::set<std::string>& aColumns )
 
  218        nanodbc::catalog catalog( *
m_conn );
 
  219        nanodbc::catalog::tables tables = catalog.find_tables( 
fromUTF8( aTable ) );
 
  223            wxLogTrace( 
traceDatabase, wxT( 
"CacheTableInfo: table '%s' not found in catalog" ),
 
  228        std::string key = 
toUTF8( tables.table_name() );
 
  233            nanodbc::catalog::columns columns =
 
  234                    catalog.find_columns( NANODBC_TEXT( 
"" ), tables.table_name() );
 
  236            while( columns.next() )
 
  238                std::string columnKey = 
toUTF8( columns.column_name() );
 
  240                if( aColumns.count( boost::to_lower_copy( columnKey ) ) )
 
  245        catch( nanodbc::database_error& e )
 
  248            wxLogTrace( 
traceDatabase, wxT( 
"Exception while syncing columns for table '%s': %s" ),
 
  253    catch( std::exception& e )
 
 
  271        nanodbc::string qc = 
m_conn->get_info<nanodbc::string>( SQL_IDENTIFIER_QUOTE_CHAR );
 
  280    catch( std::exception& e )
 
 
  295        wxLogTrace( 
traceDatabase, wxT( 
"columnsFor: requested table %s missing from cache!" ),
 
  302        wxLogTrace( 
traceDatabase, wxT( 
"columnsFor: requested table %s has no columns mapped!" ),
 
  309    for( 
const auto& [ columnName, columnType ] : 
m_columnCache[aTable] )
 
  313    ret.resize( ret.length() - 2 );
 
 
  321                                     const std::pair<std::string, std::string>& aWhere,
 
  326        wxLogTrace( 
traceDatabase, wxT( 
"Called SelectOne without valid connection!" ) );
 
  330    auto tableMapIter = 
m_tables.find( aTable );
 
  332    if( tableMapIter == 
m_tables.end() )
 
  334        wxLogTrace( 
traceDatabase, wxT( 
"SelectOne: requested table %s not found in cache" ),
 
  339    const std::string& tableName = tableMapIter->first;
 
  342    if( 
m_cache->Get( tableName, cacheEntry ) )
 
  344        if( cacheEntry.count( aWhere.second ) )
 
  346            wxLogTrace( 
traceDatabase, wxT( 
"SelectOne: `%s` with parameter `%s` - cache hit" ),
 
  347                        tableName, aWhere.second );
 
  348            aResult = cacheEntry.at( aWhere.second );
 
  354        wxLogTrace( 
traceDatabase, wxT( 
"SelectOne: table `%s` not in row cache; will SelectAll" ),
 
  355                    tableName, aWhere.second );
 
  359        if( 
m_cache->Get( tableName, cacheEntry ) )
 
  361            if( cacheEntry.count( aWhere.second ) )
 
  363                wxLogTrace( 
traceDatabase, wxT( 
"SelectOne: `%s` with parameter `%s` - cache hit" ),
 
  364                            tableName, aWhere.second );
 
  365                aResult = cacheEntry.at( aWhere.second );
 
  373        wxLogTrace( 
traceDatabase, wxT( 
"SelectOne: requested table %s missing from column cache" ),
 
  378    auto columnCacheIter = 
m_columnCache.at( tableName ).find( aWhere.first );
 
  382        wxLogTrace( 
traceDatabase, wxT( 
"SelectOne: requested column %s not found in cache for %s" ),
 
  383                    aWhere.first, tableName );
 
  387    const std::string& columnName = columnCacheIter->first;
 
  389    std::string cacheKey = fmt::format( 
"{}{}{}", tableName, columnName, aWhere.second );
 
  391    std::string queryStr = fmt::format( 
"SELECT {} FROM {}{}{} WHERE {}{}{} = ?",
 
  395    nanodbc::string query = 
fromUTF8( queryStr );
 
  398    nanodbc::statement statement;
 
  402        statement.prepare( *
m_conn, query );
 
  403        statement.bind( 0, aWhere.second.c_str() );
 
  405    catch( std::exception& e )
 
  408        wxLogTrace( 
traceDatabase, wxT( 
"Exception while preparing statement for SelectOne: %s" ),
 
  420    nanodbc::result results;
 
  424        results = nanodbc::execute( statement );
 
  426    catch( std::exception& e )
 
  429        wxLogTrace( 
traceDatabase, wxT( 
"Exception while executing statement for SelectOne: %s" ),
 
  441    if( !results.first() )
 
  443        wxLogTrace( 
traceDatabase, wxT( 
"SelectOne: no results returned from query" ) );
 
  447    wxLogTrace( 
traceDatabase, wxT( 
"SelectOne: %ld results returned from query in %0.1f ms" ),
 
  448                results.rows(), timer.
msecs() );
 
  454        for( 
short i = 0; i < results.columns(); ++i )
 
  456            std::string column = 
toUTF8( results.column_name( i ) );
 
  458            switch( results.column_datatype( i ) )
 
  468                    aResult[column] = fmt::format( 
"{:G}", results.get<
double>( i ) );
 
  470                catch( nanodbc::null_access_error& )
 
  473                    aResult[column] = std::string();
 
  480                aResult[column] = 
toUTF8( results.get<nanodbc::string>( i, NANODBC_TEXT( 
"" ) ) );
 
  484    catch( std::exception& e )
 
  487        wxLogTrace( 
traceDatabase, wxT( 
"Exception while parsing results from SelectOne: %s" ),
 
 
  500        nanodbc::statement statement( *
m_conn );
 
  502        nanodbc::string query = 
fromUTF8( fmt::format( 
"SELECT {} FROM {}{}{}",
 
  512            statement.prepare( query );
 
  514        catch( std::exception& e )
 
  518                        wxT( 
"Exception while preparing query for selectAllAndCache: %s" ),
 
  527        nanodbc::result results;
 
  531            results = nanodbc::execute( statement );
 
  533        catch( std::exception& e )
 
  537                        wxT( 
"Exception while executing query for selectAllAndCache: %s" ),
 
  550        auto handleException =
 
  551                [&]( std::runtime_error& aException, 
const std::string& aExtraContext = 
"" )
 
  554                    std::string extra = aExtraContext.empty() ? 
"" : 
": " + aExtraContext;
 
  556                                wxT( 
"Exception while parsing result %d from selectAllAndCache: %s%s" ),
 
  560        while( results.next() )
 
  562            short columnCount = 0;
 
  567                columnCount = results.columns();
 
  569            catch( nanodbc::database_error& e )
 
  571                handleException( e );
 
  575            for( 
short j = 0; j < columnCount; ++j )
 
  578                std::string columnExtraDbgInfo;
 
  579                int datatype = SQL_UNKNOWN_TYPE;
 
  583                    column = 
toUTF8( results.column_name( j ) );
 
  584                    datatype = results.column_datatype( j );
 
  585                    columnExtraDbgInfo = fmt::format( 
"column index {}, name '{}', type {}",
 
  590                catch( nanodbc::index_range_error& e )
 
  592                    handleException( e, columnExtraDbgInfo );
 
  605                        result[column] = fmt::format( 
"{:G}", results.get<
double>( j ) );
 
  607                    catch( nanodbc::null_access_error& )
 
  610                        result[column] = std::string();
 
  612                    catch( std::runtime_error& e )
 
  614                        handleException( e, columnExtraDbgInfo );
 
  623                        result[column] = 
toUTF8( results.get<nanodbc::string>( j, NANODBC_TEXT( 
"" ) ) );
 
  625                    catch( std::runtime_error& e )
 
  627                        handleException( e, columnExtraDbgInfo );
 
  633            if( !
result.count( aKey ) )
 
  636                            wxT( 
"selectAllAndCache: warning: key %s not found in result set" ), aKey );
 
  640            std::string keyStr = std::any_cast<std::string>( 
result.at( aKey ) );
 
  641            cacheEntry[keyStr] = 
result;
 
  644        wxLogTrace( 
traceDatabase, wxT( 
"selectAllAndCache from %s completed in %0.1f ms" ), aTable,
 
  647        m_cache->Put( aTable, cacheEntry );
 
  650    catch( std::exception& e )
 
 
  667        wxLogTrace( 
traceDatabase, wxT( 
"Called SelectAll without valid connection!" ) );
 
  671    auto tableMapIter = 
m_tables.find( aTable );
 
  673    if( tableMapIter == 
m_tables.end() )
 
  675        wxLogTrace( 
traceDatabase, wxT( 
"SelectAll: requested table %s not found in cache" ), aTable );
 
  681    if( !
m_cache->Get( aTable, cacheEntry ) )
 
  685            wxLogTrace( 
traceDatabase, wxT( 
"SelectAll: `%s` cache fill failed" ), aTable );
 
  690        m_cache->Get( aTable, cacheEntry );
 
  693    if( !
m_cache->Get( aTable, cacheEntry ) )
 
  695        wxLogTrace( 
traceDatabase, wxT( 
"SelectAll: `%s` failed to get results from cache!" ), aTable );
 
  699    wxLogTrace( 
traceDatabase, wxT( 
"SelectAll: `%s` - returning cached results" ), aTable );
 
  701    aResults.reserve( cacheEntry.size() );
 
  703    for( 
auto &[ key, row ] : cacheEntry )
 
  704        aResults.emplace_back( row );
 
 
std::map< std::string, ROW > CACHE_VALUE
 
bool CacheTableInfo(const std::string &aTable, const std::set< std::string > &aColumns)
 
std::string m_connectionString
 
std::map< std::string, std::any > ROW
 
std::map< std::string, std::map< std::string, int > > m_columnCache
Map of table -> map of column name -> data type.
 
void SetCacheParams(int aMaxSize, int aMaxAge)
 
bool selectAllAndCache(const std::string &aTable, const std::string &aKey)
 
bool SelectAll(const std::string &aTable, const std::string &aKey, std::vector< ROW > &aResults)
Retrieves all rows from a database table.
 
DATABASE_CONNECTION(const std::string &aDataSourceName, const std::string &aUsername, const std::string &aPassword, int aTimeoutSeconds=DEFAULT_TIMEOUT, bool aConnectNow=true)
 
std::unique_ptr< DB_CACHE_TYPE > m_cache
 
std::string columnsFor(const std::string &aTable)
 
std::unique_ptr< nanodbc::connection > m_conn
 
bool SelectOne(const std::string &aTable, const std::pair< std::string, std::string > &aWhere, ROW &aResult)
Retrieves a single row from a database table.
 
std::map< std::string, std::string > m_tables
 
A small class to help profiling.
 
void Stop()
Save the time when this function was called, and set the counter stane to stop.
 
double msecs(bool aSinceLast=false)
 
const char *const traceDatabase
 
std::string toUTF8(const nanodbc::string &aString)
Converts a string from nanodbc-native to KiCad-native.
 
nanodbc::string fromUTF8(const std::string &aString)
When Unicode support is enabled in nanodbc, string formats are used matching the appropriate characte...
 
const char *const traceDatabase
 
static bool empty(const wxTextEntryBase *aCtrl)
 
wxString result
Test unit parsing edge cases and error handling.