21#include <boost/locale.hpp>
23#include <nanodbc/nanodbc.h>
28#define UINT64 uint64_t
65nanodbc::string
fromUTF8(
const std::string& aString )
67 return boost::locale::conv::utf_to_utf<nanodbc::string::value_type>( aString );
76std::string
toUTF8(
const nanodbc::string& aString )
78 return boost::locale::conv::utf_to_utf<char>( aString );
83 const std::string& aUsername,
84 const std::string& aPassword,
int aTimeoutSeconds,
88 m_dsn = aDataSourceName;
101 int aTimeoutSeconds,
bool aConnectNow ) :
123 m_cache = std::make_unique<DB_CACHE_TYPE>( 10, 1 );
138 m_cache->SetMaxSize(
static_cast<size_t>( aMaxSize ) );
139 m_cache->SetMaxAge(
static_cast<time_t
>( aMaxAge ) );
155 m_conn = std::make_unique<nanodbc::connection>( dsn, user, pass,
m_timeout );
159 wxLogTrace(
traceDatabase, wxT(
"Creating connection with connection string" ) );
163 catch( nanodbc::database_error& e )
182 wxLogTrace(
traceDatabase, wxT(
"Note: Disconnect() called without valid connection" ) );
190 catch( boost::locale::conv::conversion_error& exc )
192 wxLogTrace(
traceDatabase, wxT(
"Disconnect() error \"%s\" occured." ), exc.what() );
196 return !
m_conn->connected();
205 return m_conn->connected();
210 const std::set<std::string>& aColumns )
217 nanodbc::catalog catalog( *
m_conn );
218 nanodbc::catalog::tables tables = catalog.find_tables(
fromUTF8( aTable ) );
222 wxLogTrace(
traceDatabase, wxT(
"CacheTableInfo: table '%s' not found in catalog" ),
227 std::string key =
toUTF8( tables.table_name() );
232 nanodbc::catalog::columns columns =
233 catalog.find_columns( NANODBC_TEXT(
"" ), tables.table_name() );
235 while( columns.next() )
237 std::string columnKey =
toUTF8( columns.column_name() );
239 if( aColumns.count( columnKey ) )
244 catch( nanodbc::database_error& e )
247 wxLogTrace(
traceDatabase, wxT(
"Exception while syncing columns for table '%s': %s" ),
252 catch( nanodbc::database_error& e )
270 nanodbc::string qc =
m_conn->get_info<nanodbc::string>( SQL_IDENTIFIER_QUOTE_CHAR );
279 catch( nanodbc::database_error& )
293 wxLogTrace(
traceDatabase, wxT(
"columnsFor: requested table %s missing from cache!" ),
300 wxLogTrace(
traceDatabase, wxT(
"columnsFor: requested table %s has no columns mapped!" ),
307 for(
const auto& [ columnName, columnType ] :
m_columnCache[aTable] )
311 ret.resize( ret.length() - 2 );
319 const std::pair<std::string, std::string>& aWhere,
324 wxLogTrace(
traceDatabase, wxT(
"Called SelectOne without valid connection!" ) );
328 auto tableMapIter =
m_tables.find( aTable );
330 if( tableMapIter ==
m_tables.end() )
332 wxLogTrace(
traceDatabase, wxT(
"SelectOne: requested table %s not found in cache" ),
337 const std::string& tableName = tableMapIter->first;
340 if(
m_cache->Get( tableName, cacheEntry ) )
342 if( cacheEntry.count( aWhere.second ) )
344 wxLogTrace(
traceDatabase, wxT(
"SelectOne: `%s` with parameter `%s` - cache hit" ),
345 tableName, aWhere.second );
346 aResult = cacheEntry.at( aWhere.second );
352 wxLogTrace(
traceDatabase, wxT(
"SelectOne: table `%s` not in row cache; will SelectAll" ),
353 tableName, aWhere.second );
357 if(
m_cache->Get( tableName, cacheEntry ) )
359 if( cacheEntry.count( aWhere.second ) )
361 wxLogTrace(
traceDatabase, wxT(
"SelectOne: `%s` with parameter `%s` - cache hit" ),
362 tableName, aWhere.second );
363 aResult = cacheEntry.at( aWhere.second );
371 wxLogTrace(
traceDatabase, wxT(
"SelectOne: requested table %s missing from column cache" ),
376 auto columnCacheIter =
m_columnCache.at( tableName ).find( aWhere.first );
380 wxLogTrace(
traceDatabase, wxT(
"SelectOne: requested column %s not found in cache for %s" ),
381 aWhere.first, tableName );
385 const std::string& columnName = columnCacheIter->first;
387 std::string cacheKey = fmt::format(
"{}{}{}", tableName, columnName, aWhere.second );
389 std::string queryStr = fmt::format(
"SELECT {} FROM {}{}{} WHERE {}{}{} = ?",
393 nanodbc::string query =
fromUTF8( queryStr );
396 nanodbc::statement statement;
400 statement.prepare( *
m_conn, query );
401 statement.bind( 0, aWhere.second.c_str() );
403 catch( nanodbc::database_error& e )
406 wxLogTrace(
traceDatabase, wxT(
"Exception while preparing statement for SelectOne: %s" ),
418 nanodbc::result results;
422 results = nanodbc::execute( statement );
424 catch( nanodbc::database_error& e )
427 wxLogTrace(
traceDatabase, wxT(
"Exception while executing statement for SelectOne: %s" ),
439 if( !results.first() )
441 wxLogTrace(
traceDatabase, wxT(
"SelectOne: no results returned from query" ) );
445 wxLogTrace(
traceDatabase, wxT(
"SelectOne: %ld results returned from query in %0.1f ms" ),
446 results.rows(), timer.
msecs() );
452 for(
short i = 0; i < results.columns(); ++i )
454 std::string column =
toUTF8( results.column_name( i ) );
456 switch( results.column_datatype( i ) )
466 aResult[column] = fmt::format(
"{:G}", results.get<
double>( i ) );
468 catch( nanodbc::null_access_error& )
471 aResult[column] = std::string();
478 aResult[column] =
toUTF8( results.get<nanodbc::string>( i, NANODBC_TEXT(
"" ) ) );
482 catch( nanodbc::database_error& e )
485 wxLogTrace(
traceDatabase, wxT(
"Exception while parsing results from SelectOne: %s" ),
496 nanodbc::statement statement( *
m_conn );
498 nanodbc::string query =
fromUTF8( fmt::format(
"SELECT {} FROM {}{}{}",
columnsFor( aTable ),
507 statement.prepare( query );
509 catch( nanodbc::database_error& e )
513 wxT(
"Exception while preparing query for selectAllAndCache: %s" ),
522 nanodbc::result results;
526 results = nanodbc::execute( statement );
528 catch( nanodbc::database_error& e )
532 wxT(
"Exception while executing query for selectAllAndCache: %s" ),
545 auto handleException =
546 [&]( std::runtime_error& aException,
const std::string& aExtraContext =
"" )
549 std::string extra = aExtraContext.empty() ?
"" :
": " + aExtraContext;
551 wxT(
"Exception while parsing result %d from selectAllAndCache: %s%s" ),
555 while( results.next() )
557 short columnCount = 0;
562 columnCount = results.columns();
564 catch( nanodbc::database_error& e )
566 handleException( e );
570 for(
short j = 0; j < columnCount; ++j )
573 std::string columnExtraDbgInfo;
574 int datatype = SQL_UNKNOWN_TYPE;
578 column =
toUTF8( results.column_name( j ) );
579 datatype = results.column_datatype( j );
580 columnExtraDbgInfo = fmt::format(
"column index {}, name '{}', type {}", j, column,
583 catch( nanodbc::index_range_error& e )
585 handleException( e, columnExtraDbgInfo );
599 result[column] = fmt::format(
"{:G}", results.get<
double>( j ) );
601 catch( nanodbc::null_access_error& )
604 result[column] = std::string();
606 catch( std::runtime_error& e )
608 handleException( e, columnExtraDbgInfo );
618 result[column] =
toUTF8( results.get<nanodbc::string>( j,
619 NANODBC_TEXT(
"" ) ) );
621 catch( std::runtime_error& e )
623 handleException( e, columnExtraDbgInfo );
630 wxASSERT( result.count( aKey ) );
631 std::string keyStr = std::any_cast<std::string>( result.at( aKey ) );
632 cacheEntry[keyStr] = result;
635 wxLogTrace(
traceDatabase, wxT(
"selectAllAndCache from %s completed in %0.1f ms" ), aTable,
638 m_cache->Put( aTable, cacheEntry );
644 std::vector<ROW>& aResults )
648 wxLogTrace(
traceDatabase, wxT(
"Called SelectAll without valid connection!" ) );
652 auto tableMapIter =
m_tables.find( aTable );
654 if( tableMapIter ==
m_tables.end() )
656 wxLogTrace(
traceDatabase, wxT(
"SelectAll: requested table %s not found in cache" ),
663 if( !
m_cache->Get( aTable, cacheEntry ) )
667 wxLogTrace(
traceDatabase, wxT(
"SelectAll: `%s` cache fill failed" ), aTable );
672 m_cache->Get( aTable, cacheEntry );
675 if( !
m_cache->Get( aTable, cacheEntry ) )
677 wxLogTrace(
traceDatabase, wxT(
"SelectAll: `%s` failed to get results from cache!" ),
682 wxLogTrace(
traceDatabase, wxT(
"SelectAll: `%s` - returning cached results" ), aTable );
684 aResults.reserve( cacheEntry.size() );
686 for(
auto &[ key, row ] : cacheEntry )
687 aResults.emplace_back( row );
CacheValueType 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)