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( nanodbc::database_error& e )
183 wxLogTrace(
traceDatabase, wxT(
"Note: Disconnect() called without valid connection" ) );
191 catch( boost::locale::conv::conversion_error& 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( nanodbc::database_error& e )
271 nanodbc::string qc =
m_conn->get_info<nanodbc::string>( SQL_IDENTIFIER_QUOTE_CHAR );
280 catch( nanodbc::database_error& )
294 wxLogTrace(
traceDatabase, wxT(
"columnsFor: requested table %s missing from cache!" ),
301 wxLogTrace(
traceDatabase, wxT(
"columnsFor: requested table %s has no columns mapped!" ),
308 for(
const auto& [ columnName, columnType ] :
m_columnCache[aTable] )
312 ret.resize( ret.length() - 2 );
320 const std::pair<std::string, std::string>& aWhere,
325 wxLogTrace(
traceDatabase, wxT(
"Called SelectOne without valid connection!" ) );
329 auto tableMapIter =
m_tables.find( aTable );
331 if( tableMapIter ==
m_tables.end() )
333 wxLogTrace(
traceDatabase, wxT(
"SelectOne: requested table %s not found in cache" ),
338 const std::string& tableName = tableMapIter->first;
341 if(
m_cache->Get( tableName, cacheEntry ) )
343 if( cacheEntry.count( aWhere.second ) )
345 wxLogTrace(
traceDatabase, wxT(
"SelectOne: `%s` with parameter `%s` - cache hit" ),
346 tableName, aWhere.second );
347 aResult = cacheEntry.at( aWhere.second );
353 wxLogTrace(
traceDatabase, wxT(
"SelectOne: table `%s` not in row cache; will SelectAll" ),
354 tableName, aWhere.second );
358 if(
m_cache->Get( tableName, cacheEntry ) )
360 if( cacheEntry.count( aWhere.second ) )
362 wxLogTrace(
traceDatabase, wxT(
"SelectOne: `%s` with parameter `%s` - cache hit" ),
363 tableName, aWhere.second );
364 aResult = cacheEntry.at( aWhere.second );
372 wxLogTrace(
traceDatabase, wxT(
"SelectOne: requested table %s missing from column cache" ),
377 auto columnCacheIter =
m_columnCache.at( tableName ).find( aWhere.first );
381 wxLogTrace(
traceDatabase, wxT(
"SelectOne: requested column %s not found in cache for %s" ),
382 aWhere.first, tableName );
386 const std::string& columnName = columnCacheIter->first;
388 std::string cacheKey = fmt::format(
"{}{}{}", tableName, columnName, aWhere.second );
390 std::string queryStr = fmt::format(
"SELECT {} FROM {}{}{} WHERE {}{}{} = ?",
394 nanodbc::string query =
fromUTF8( queryStr );
397 nanodbc::statement statement;
401 statement.prepare( *
m_conn, query );
402 statement.bind( 0, aWhere.second.c_str() );
404 catch( nanodbc::database_error& e )
407 wxLogTrace(
traceDatabase, wxT(
"Exception while preparing statement for SelectOne: %s" ),
419 nanodbc::result results;
423 results = nanodbc::execute( statement );
425 catch( nanodbc::database_error& e )
428 wxLogTrace(
traceDatabase, wxT(
"Exception while executing statement for SelectOne: %s" ),
440 if( !results.first() )
442 wxLogTrace(
traceDatabase, wxT(
"SelectOne: no results returned from query" ) );
446 wxLogTrace(
traceDatabase, wxT(
"SelectOne: %ld results returned from query in %0.1f ms" ),
447 results.rows(), timer.
msecs() );
453 for(
short i = 0; i < results.columns(); ++i )
455 std::string column =
toUTF8( results.column_name( i ) );
457 switch( results.column_datatype( i ) )
467 aResult[column] = fmt::format(
"{:G}", results.get<
double>( i ) );
469 catch( nanodbc::null_access_error& )
472 aResult[column] = std::string();
479 aResult[column] =
toUTF8( results.get<nanodbc::string>( i, NANODBC_TEXT(
"" ) ) );
483 catch( nanodbc::database_error& e )
486 wxLogTrace(
traceDatabase, wxT(
"Exception while parsing results from SelectOne: %s" ),
497 nanodbc::statement statement( *
m_conn );
499 nanodbc::string query =
fromUTF8( fmt::format(
"SELECT {} FROM {}{}{}",
columnsFor( aTable ),
508 statement.prepare( query );
510 catch( nanodbc::database_error& e )
514 wxT(
"Exception while preparing query for selectAllAndCache: %s" ),
523 nanodbc::result results;
527 results = nanodbc::execute( statement );
529 catch( nanodbc::database_error& e )
533 wxT(
"Exception while executing query for selectAllAndCache: %s" ),
546 auto handleException =
547 [&]( std::runtime_error& aException,
const std::string& aExtraContext =
"" )
550 std::string extra = aExtraContext.empty() ?
"" :
": " + aExtraContext;
552 wxT(
"Exception while parsing result %d from selectAllAndCache: %s%s" ),
556 while( results.next() )
558 short columnCount = 0;
563 columnCount = results.columns();
565 catch( nanodbc::database_error& e )
567 handleException( e );
571 for(
short j = 0; j < columnCount; ++j )
574 std::string columnExtraDbgInfo;
575 int datatype = SQL_UNKNOWN_TYPE;
579 column =
toUTF8( results.column_name( j ) );
580 datatype = results.column_datatype( j );
581 columnExtraDbgInfo = fmt::format(
"column index {}, name '{}', type {}", j, column,
584 catch( nanodbc::index_range_error& e )
586 handleException( e, columnExtraDbgInfo );
600 result[column] = fmt::format(
"{:G}", results.get<
double>( j ) );
602 catch( nanodbc::null_access_error& )
605 result[column] = std::string();
607 catch( std::runtime_error& e )
609 handleException( e, columnExtraDbgInfo );
619 result[column] =
toUTF8( results.get<nanodbc::string>( j,
620 NANODBC_TEXT(
"" ) ) );
622 catch( std::runtime_error& e )
624 handleException( e, columnExtraDbgInfo );
631 if( !result.count( aKey ) )
634 wxT(
"selectAllAndCache: warning: key %s not found in result set" ), aKey );
638 std::string keyStr = std::any_cast<std::string>( result.at( aKey ) );
639 cacheEntry[keyStr] = result;
642 wxLogTrace(
traceDatabase, wxT(
"selectAllAndCache from %s completed in %0.1f ms" ), aTable,
645 m_cache->Put( aTable, cacheEntry );
651 std::vector<ROW>& aResults )
655 wxLogTrace(
traceDatabase, wxT(
"Called SelectAll without valid connection!" ) );
659 auto tableMapIter =
m_tables.find( aTable );
661 if( tableMapIter ==
m_tables.end() )
663 wxLogTrace(
traceDatabase, wxT(
"SelectAll: requested table %s not found in cache" ),
670 if( !
m_cache->Get( aTable, cacheEntry ) )
674 wxLogTrace(
traceDatabase, wxT(
"SelectAll: `%s` cache fill failed" ), aTable );
679 m_cache->Get( aTable, cacheEntry );
682 if( !
m_cache->Get( aTable, cacheEntry ) )
684 wxLogTrace(
traceDatabase, wxT(
"SelectAll: `%s` failed to get results from cache!" ),
689 wxLogTrace(
traceDatabase, wxT(
"SelectAll: `%s` - returning cached results" ), aTable );
691 aResults.reserve( cacheEntry.size() );
693 for(
auto &[ key, row ] : cacheEntry )
694 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)