21#include <boost/algorithm/string.hpp>
22#include <boost/locale.hpp>
23#include <fmt/format.h>
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>& aRequiredColumns,
212 const std::set<std::string>& aOptionalColumns )
220 std::set<std::string> requiredLower;
221 std::set<std::string> optionalLower;
223 for(
const std::string& col : aRequiredColumns )
224 requiredLower.insert( boost::to_lower_copy( col ) );
226 for(
const std::string& col : aOptionalColumns )
227 optionalLower.insert( boost::to_lower_copy( col ) );
231 nanodbc::catalog catalog( *
m_conn );
232 nanodbc::catalog::tables tables = catalog.find_tables(
fromUTF8( aTable ) );
236 wxLogTrace(
traceDatabase, wxT(
"CacheTableInfo: table '%s' not found in catalog" ),
241 std::string key =
toUTF8( tables.table_name() );
246 nanodbc::catalog::columns columns =
247 catalog.find_columns( NANODBC_TEXT(
"" ), tables.table_name() );
249 std::set<std::string> columnsInCatalog;
251 while( columns.next() )
253 std::string columnKey =
toUTF8( columns.column_name() );
254 std::string columnKeyLower = boost::to_lower_copy( columnKey );
256 if( requiredLower.count( columnKeyLower )
257 || optionalLower.count( columnKeyLower ) )
260 columnsInCatalog.insert( columnKeyLower );
266 for(
const std::string& requestedCol : aRequiredColumns )
268 std::string requestedColLower = boost::to_lower_copy( requestedCol );
270 if( !columnsInCatalog.count( requestedColLower ) && !requestedCol.empty() )
273 wxT(
"CacheTableInfo: column '%s' not found in catalog for table "
274 "'%s', adding anyway" ),
283 for(
const std::string& requestedCol : aOptionalColumns )
285 std::string requestedColLower = boost::to_lower_copy( requestedCol );
287 if( !columnsInCatalog.count( requestedColLower ) && !requestedCol.empty() )
289 wxLogWarning( wxT(
"Database table '%s' has no column '%s'; ignoring the "
290 "misconfigured properties mapping." ),
295 catch( nanodbc::database_error& e )
298 wxLogTrace(
traceDatabase, wxT(
"Exception while syncing columns for table '%s': %s" ),
303 catch( std::exception& e )
321 nanodbc::string qc =
m_conn->get_info<nanodbc::string>( SQL_IDENTIFIER_QUOTE_CHAR );
330 catch( std::exception& e )
345 wxLogTrace(
traceDatabase, wxT(
"columnsFor: requested table %s missing from cache!" ),
352 wxLogTrace(
traceDatabase, wxT(
"columnsFor: requested table %s has no columns mapped!" ),
359 for(
const auto& [ columnName, columnType ] :
m_columnCache[aTable] )
363 ret.resize( ret.length() - 2 );
371 const std::pair<std::string, std::string>& aWhere,
378 wxLogTrace(
traceDatabase, wxT(
"Called SelectOne without valid connection!" ) );
382 auto tableMapIter =
m_tables.find( aTable );
384 if( tableMapIter ==
m_tables.end() )
386 wxLogTrace(
traceDatabase, wxT(
"SelectOne: requested table %s not found in cache" ),
391 const std::string& tableName = tableMapIter->first;
394 if(
m_cache->Get( tableName, cacheEntry ) )
396 if( cacheEntry.count( aWhere.second ) )
398 wxLogTrace(
traceDatabase, wxT(
"SelectOne: `%s` with parameter `%s` - cache hit" ),
399 tableName, aWhere.second );
400 aResult = cacheEntry.at( aWhere.second );
406 wxLogTrace(
traceDatabase, wxT(
"SelectOne: table `%s` not in row cache; will SelectAll" ),
407 tableName, aWhere.second );
411 if(
m_cache->Get( tableName, cacheEntry ) )
413 if( cacheEntry.count( aWhere.second ) )
415 wxLogTrace(
traceDatabase, wxT(
"SelectOne: `%s` with parameter `%s` - cache hit" ),
416 tableName, aWhere.second );
417 aResult = cacheEntry.at( aWhere.second );
425 wxLogTrace(
traceDatabase, wxT(
"SelectOne: requested table %s missing from column cache" ),
430 auto columnCacheIter =
m_columnCache.at( tableName ).find( aWhere.first );
434 wxLogTrace(
traceDatabase, wxT(
"SelectOne: requested column %s not found in cache for %s" ),
435 aWhere.first, tableName );
439 const std::string& columnName = columnCacheIter->first;
441 std::string cacheKey = fmt::format(
"{}{}{}", tableName, columnName, aWhere.second );
443 std::string queryStr = fmt::format(
"SELECT {} FROM {}{}{} WHERE {}{}{} = ?",
447 nanodbc::string query =
fromUTF8( queryStr );
450 nanodbc::statement statement;
454 statement.prepare( *
m_conn, query );
456 catch( std::exception& e )
459 wxLogTrace(
traceDatabase, wxT(
"Exception while preparing statement for SelectOne: %s" ),
472 statement.describe_parameters( { 0 }, { SQL_VARCHAR }, { 255 }, { 0 } );
473 statement.bind( 0, aWhere.second.c_str() );
475 catch( std::exception& e )
478 wxLogTrace(
traceDatabase, wxT(
"Exception while binding parameter for SelectOne: %s" ),
489 nanodbc::result results;
493 results = nanodbc::execute( statement );
495 catch( std::exception& e )
498 wxLogTrace(
traceDatabase, wxT(
"Exception while executing statement for SelectOne: %s" ),
510 if( !results.first() )
512 wxLogTrace(
traceDatabase, wxT(
"SelectOne: no results returned from query" ) );
516 wxLogTrace(
traceDatabase, wxT(
"SelectOne: %ld results returned from query in %0.1f ms" ),
517 results.rows(), timer.
msecs() );
523 for(
short i = 0; i < results.columns(); ++i )
525 std::string column =
toUTF8( results.column_name( i ) );
527 switch( results.column_datatype( i ) )
537 aResult[column] = fmt::format(
"{:G}", results.get<
double>( i ) );
539 catch( nanodbc::null_access_error& )
542 aResult[column] = std::string();
549 aResult[column] =
toUTF8( results.get<nanodbc::string>( i, NANODBC_TEXT(
"" ) ) );
553 catch( std::exception& e )
556 wxLogTrace(
traceDatabase, wxT(
"Exception while parsing results from SelectOne: %s" ),
569 nanodbc::statement statement( *
m_conn );
571 nanodbc::string query =
fromUTF8( fmt::format(
"SELECT {} FROM {}{}{}",
579 statement.prepare( query );
581 catch( std::exception& e )
585 wxT(
"Exception while preparing query for selectAllAndCache: %s" ),
594 nanodbc::result results;
598 results = nanodbc::execute( statement );
600 catch( std::exception& e )
604 wxT(
"Exception while executing query for selectAllAndCache: %s" ),
617 auto handleException =
618 [&]( std::runtime_error& aException,
const std::string& aExtraContext =
"" )
621 std::string extra = aExtraContext.empty() ?
"" :
": " + aExtraContext;
623 wxT(
"Exception while parsing result %d from selectAllAndCache: %s%s" ),
627 while( results.next() )
629 short columnCount = 0;
634 columnCount = results.columns();
636 catch( nanodbc::database_error& e )
638 handleException( e );
642 for(
short j = 0; j < columnCount; ++j )
645 std::string columnExtraDbgInfo;
646 int datatype = SQL_UNKNOWN_TYPE;
650 column =
toUTF8( results.column_name( j ) );
651 datatype = results.column_datatype( j );
652 columnExtraDbgInfo = fmt::format(
"column index {}, name '{}', type {}",
657 catch( nanodbc::index_range_error& e )
659 handleException( e, columnExtraDbgInfo );
672 result[column] = fmt::format(
"{:G}", results.get<
double>( j ) );
674 catch( nanodbc::null_access_error& )
677 result[column] = std::string();
679 catch( std::runtime_error& e )
681 handleException( e, columnExtraDbgInfo );
690 result[column] =
toUTF8( results.get<nanodbc::string>( j, NANODBC_TEXT(
"" ) ) );
692 catch( std::runtime_error& e )
694 handleException( e, columnExtraDbgInfo );
700 if( !
result.count( aKey ) )
703 wxT(
"selectAllAndCache: warning: key %s not found in result set" ), aKey );
707 std::string keyStr = std::any_cast<std::string>(
result.at( aKey ) );
708 cacheEntry[keyStr] =
result;
711 wxLogTrace(
traceDatabase, wxT(
"selectAllAndCache from %s completed in %0.1f ms" ), aTable,
714 m_cache->Put( aTable, cacheEntry );
717 catch( std::exception& e )
736 wxLogTrace(
traceDatabase, wxT(
"Called SelectAll without valid connection!" ) );
740 auto tableMapIter =
m_tables.find( aTable );
742 if( tableMapIter ==
m_tables.end() )
744 wxLogTrace(
traceDatabase, wxT(
"SelectAll: requested table %s not found in cache" ), aTable );
750 if( !
m_cache->Get( aTable, cacheEntry ) )
754 wxLogTrace(
traceDatabase, wxT(
"SelectAll: `%s` cache fill failed" ), aTable );
759 m_cache->Get( aTable, cacheEntry );
763 wxLogTrace(
traceDatabase, wxT(
"SelectAll: `%s` - returning cached results" ), aTable );
766 if( !
m_cache->Get( aTable, cacheEntry ) )
768 wxLogTrace(
traceDatabase, wxT(
"SelectAll: `%s` failed to get results from cache!" ), aTable );
772 aResults.reserve( cacheEntry.size() );
774 for(
auto &[ key, row ] : cacheEntry )
775 aResults.emplace_back( row );
std::map< std::string, ROW > CACHE_VALUE
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)
The caller must already hold m_queryMutex.
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.
bool CacheTableInfo(const std::string &aTable, const std::set< std::string > &aRequiredColumns, const std::set< std::string > &aOptionalColumns={})
Caches schema information for a table so that subsequent queries know which columns exist.
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.