30#include <boost/algorithm/string.hpp>
31#include <boost/locale.hpp>
32#include <fmt/format.h>
33#include <nanodbc/nanodbc.h>
61static nanodbc::string
fromUTF8(
const std::string& aString )
63 return boost::locale::conv::utf_to_utf<nanodbc::string::value_type>( aString );
72static std::string
toUTF8(
const nanodbc::string& aString )
74 return boost::locale::conv::utf_to_utf<char>( aString );
86static std::vector<RESULT_COLUMN>
resultColumns( nanodbc::result& aResults )
88 std::vector<RESULT_COLUMN> columns;
90 for(
short i = 0; i < aResults.columns(); ++i )
91 columns.push_back( { toUTF8( aResults.column_name( i ) ), aResults.column_datatype( i ) } );
102static std::vector<DB_VALUE>
decodeRow( nanodbc::result& aResults,
const std::vector<RESULT_COLUMN>& aColumns )
104 std::vector<DB_VALUE> row;
105 row.reserve( aColumns.size() );
107 for(
short i = 0; i < static_cast<short>( aColumns.size() ); ++i )
109 switch( aColumns[i].type )
116 row.emplace_back( aResults.get<
double>( i ) );
118 catch( nanodbc::null_access_error& )
120 row.emplace_back( std::monostate() );
133 row.emplace_back(
toUTF8( aResults.get<nanodbc::string>( i, NANODBC_TEXT(
"" ) ) ) );
151 [](
const std::string& aName ) -> std::optional<std::string>
155 if( boost::iequals(
source.name, aName ) )
169std::vector<std::unique_ptr<nanodbc::connection>>& leakedConnections()
171 static auto* s_leaked =
new std::vector<std::unique_ptr<nanodbc::connection>>;
176std::mutex& leakedConnectionsMutex()
178 static auto* s_mutex =
new std::mutex;
191 std::unique_lock<std::timed_mutex> driverLock;
198 std::lock_guard<std::mutex> guard( leakedConnectionsMutex() );
199 leakedConnections().push_back( std::move(
m_conn ) );
215 wxLogTrace(
traceDatabase, wxT(
"Taking the Access driver lock" ) );
218 aLock = std::unique_lock<std::timed_mutex>(
AccessEngineMutex(), std::try_to_lock );
220 if( aLock.owns_lock() )
228 if( !aLock.owns_lock() )
230 wxLogTrace(
traceDatabase, wxT(
"The Access driver lock stayed busy" ) );
241 const size_t size = aConnectionString.size();
245 size_t end = aConnectionString.find_first_of(
"=;", pos );
247 if(
end == std::string::npos )
250 std::string key = boost::trim_copy( aConnectionString.substr( pos,
end - pos ) );
252 if( aConnectionString[
end] ==
';' )
258 pos = aConnectionString.find_first_not_of(
" \t",
end + 1 );
260 if( pos == std::string::npos )
265 if( aConnectionString[pos] ==
'{' )
268 for( ++pos; pos < size; ++pos )
270 if( aConnectionString[pos] ==
'}' )
272 if( pos + 1 < size && aConnectionString[pos + 1] ==
'}' )
283 value += aConnectionString[pos];
286 pos = aConnectionString.find(
';', pos );
290 size_t stop = aConnectionString.find(
';', pos );
291 value = boost::trim_copy( aConnectionString.substr( pos, stop - pos ) );
295 if( boost::iequals( key,
"DSN" ) )
298 if( boost::iequals( key,
"FILEDSN" ) )
301 if( boost::iequals( key,
"DRIVER" ) )
304 if( pos == std::string::npos )
319 [&](
const std::string& aName )
321 std::optional<std::string> driver = aLookup( aName );
326 if( aConnectionString.empty() )
327 return lockForDsn( aDsn );
332 switch( selector.kind )
346 return aDriverName.starts_with(
"Microsoft Access Driver" );
360 std::unique_lock<std::timed_mutex> driverLock;
374 m_conn = std::make_unique<nanodbc::connection>( dsn, user, pass,
m_timeout );
378 wxLogTrace(
traceDatabase, wxT(
"Creating connection with connection string" ) );
382 catch( nanodbc::database_error& e )
388 catch( std::exception& e )
403 catch( std::exception& e )
406 wxLogTrace(
traceDatabase, wxT(
"Pattern escape unavailable: %s" ), e.what() );
418 wxLogTrace(
traceDatabase, wxT(
"Note: Disconnect() called without valid connection" ) );
422 std::unique_lock<std::timed_mutex> driverLock;
441 catch( std::exception& exc )
443 wxLogTrace(
traceDatabase, wxT(
"Disconnect() error \"%s\" occured." ), exc.what() );
453 return m_conn->connected();
468 nanodbc::catalog catalog( *
m_conn );
473 wxLogTrace(
traceDatabase, wxT(
"DescribeTable: table '%s' not found in catalog" ), aTable );
482 nanodbc::catalog::columns columns =
488 while( columns.next() )
490 std::string
name =
toUTF8( columns.column_name() );
492 info.columns.push_back( {
name, columns.data_type() } );
493 described[
name] = { columns.data_type(), columns.column_size(), columns.decimal_digits() };
496 catch( nanodbc::database_error& e )
499 wxLogTrace(
traceDatabase, wxT(
"Exception while syncing columns for table '%s': %s" ),
info.name,
502 info.columns.clear();
503 info.columnsUnavailable =
true;
508 catch( std::exception& e )
526 nanodbc::catalog catalog( *
m_conn );
527 nanodbc::catalog::columns columns = catalog.find_columns( NANODBC_TEXT(
"" ),
530 while( columns.next() )
531 aColumns.emplace_back(
toUTF8( columns.column_name() ) );
533 catch( std::exception& e )
551 nanodbc::string qc =
m_conn->get_info<nanodbc::string>( SQL_IDENTIFIER_QUOTE_CHAR );
560 catch( std::exception& e )
574 std::string escaped = aIdentifier;
576 boost::replace_all( escaped, quote, quote + quote );
578 return quote + escaped + quote;
590 for(
char c : aName )
604 if( aColumns.empty() )
609 for(
const std::string& column : aColumns )
610 ret +=
quoted( column ) +
", ";
613 ret.resize( ret.length() - 2 );
627 short aColumnScale,
const std::string& aKey )
629 const short type =
static_cast<short>( aColumnType );
632 const unsigned long columnSize = aColumnSize > 0 ?
static_cast<unsigned long>( aColumnSize ) : 0;
642 const char*
end = aKey.data() + aKey.size();
643 auto parsed = std::from_chars( aKey.data(),
end, value );
646 if( parsed.ec == std::errc() && parsed.ptr ==
end )
657 case SQL_LONGVARCHAR:
660 case SQL_WLONGVARCHAR:
662 const bool wide = type == SQL_WCHAR || type == SQL_WVARCHAR || type == SQL_WLONGVARCHAR;
665 return {
static_cast<short>( wide ? SQL_WVARCHAR : SQL_VARCHAR ),
666 std::max( size,
static_cast<unsigned long>( aKey.size() ) ), 0,
false, 0 };
677 const std::string& aKeyColumn,
const std::string& aKey,
678 std::vector<std::string>& aLabels, std::vector<DB_VALUE>& aRow )
682 wxLogTrace(
traceDatabase, wxT(
"Called ReadRow without valid connection!" ) );
690 if(
auto column =
table->second.find( aKeyColumn ); column !=
table->second.end() )
691 columnInfo = column->second;
694 std::string queryStr = fmt::format(
"SELECT {} FROM {} WHERE {} = ?",
columnList( aColumns ),
696 nanodbc::string query =
fromUTF8( queryStr );
699 nanodbc::statement statement;
703 statement.prepare( *
m_conn, query );
705 catch( std::exception& e )
708 wxLogTrace(
traceDatabase, wxT(
"Exception while preparing statement for SelectOne: %s" ),
723 statement.describe_parameters( { 0 }, { key.
type }, { key.
size }, { key.
scale } );
728 statement.bind( 0, aKey.c_str() );
730 catch( std::exception& e )
733 wxLogTrace(
traceDatabase, wxT(
"Exception while binding parameter for SelectOne: %s" ),
741 wxLogTrace(
traceDatabase, wxT(
"SelectOne: `%s` with parameter `%s`" ),
toUTF8( query ), aKey );
743 nanodbc::result results;
747 results = nanodbc::execute( statement );
749 catch( std::exception& e )
752 wxLogTrace(
traceDatabase, wxT(
"Exception while executing statement for SelectOne: %s" ),
763 if( !results.first() )
765 wxLogTrace(
traceDatabase, wxT(
"SelectOne: no results returned from query" ) );
769 wxLogTrace(
traceDatabase, wxT(
"SelectOne: %ld results returned from query in %0.1f ms" ),
770 results.rows(), timer.
msecs() );
774 const std::vector<RESULT_COLUMN> columns =
resultColumns( results );
779 aLabels.push_back( column.name );
783 catch( std::exception& e )
786 wxLogTrace(
traceDatabase, wxT(
"Exception while parsing results from SelectOne: %s" ),
796 size_t aMaxRows, std::vector<std::string>& aLabels,
const DB_ROW_SINK& aSink )
803 const bool all = aMaxRows ==
ALL_ROWS;
807 const std::string select = fmt::format(
"SELECT {} FROM {}",
columnList( aColumns ),
quoted( aTable.
name ) );
809 nanodbc::result results;
813 nanodbc::statement statement( *
m_conn );
815 statement.prepare(
fromUTF8( select ) );
816 results = nanodbc::execute( statement );
827 catch(
const nanodbc::database_error& e )
830 wxLogTrace(
traceDatabase, wxT(
"ReadTable: ordered query on %s failed (%s); retrying unordered" ),
831 aTable.
name, e.what() );
838 const std::vector<RESULT_COLUMN> columns =
resultColumns( results );
841 aLabels.push_back( column.name );
845 while(
rows < aMaxRows && results.next() )
851 catch( std::runtime_error& e )
854 wxLogTrace(
traceDatabase, wxT(
"Exception while parsing result %zu from ReadTable: %s" ),
rows,
862 wxLogTrace(
traceDatabase, wxT(
"ReadTable from %s completed in %0.1f ms" ), aTable.
name, timer.
msecs() );
864 catch( std::exception& e )
889 nanodbc::catalog catalog( *
m_conn );
890 nanodbc::string current;
894 current =
m_conn->catalog_name();
896 catch(
const std::exception& )
902 [&](
const nanodbc::string& aCatalog )
904 nanodbc::catalog::tables tables = catalog.find_tables( NANODBC_TEXT(
"" ),
905 NANODBC_TEXT(
"TABLE,VIEW" ),
906 NANODBC_TEXT(
"" ), aCatalog );
908 while( tables.next() )
909 aTables.emplace_back(
toUTF8( tables.table_name() ) );
917 if( aTables.empty() && !current.empty() )
918 listTables( NANODBC_TEXT(
"" ) );
920 catch( std::exception& e )
927 std::sort( aTables.begin(), aTables.end() );
934 std::vector<DATA_SOURCE> sources;
938 for(
const nanodbc::datasource&
source : nanodbc::list_datasources() )
941 catch( std::exception& e )
943 wxLogTrace(
traceDatabase, wxT(
"ListDataSourceDetails failed: %s" ), e.what() );
952 std::vector<std::string> names;
956 for(
const nanodbc::driver& driver : nanodbc::list_drivers() )
957 names.emplace_back(
toUTF8( driver.name ) );
959 catch( std::exception& e )
961 wxLogTrace(
traceDatabase, wxT(
"ListDrivers failed: %s" ), e.what() );
972 static const std::string ACCESS_TAG =
"[odbc microsoft access driver]";
975 static const std::regex LOCKED(
976 R
"(\s*(could not use '[^\r\n]*'; file already in use)"
977 R"(|could not lock file)"
978 R"(|database '[^\r\n]*' is exclusively locked)"
979 R"(|you attempted to open a database that is already opened (exclusively )?by user '[^\r\n]*')"
980 R"( on machine '[^\r\n]*')"
981 R"(|the database '[^\r\n]*' is already opened by user '[^\r\n]*' on machine '[^\r\n]*')"
982 R"(|the database has been placed in a state by user '[^\r\n]*' on machine '[^\r\n]*')"
983 R"( that prevents it from being opened or locked)\.?\s*)"
984 R"((try again when the database is available\.?\s*)?)" );
986 const size_t tag = aLowerMessage.rfind( ACCESS_TAG );
988 return tag != std::string::npos
989 && std::regex_match( aLowerMessage.begin() + tag + ACCESS_TAG.size(), aLowerMessage.end(), LOCKED );
995 std::string
message = boost::to_lower_copy( aMessage );
998 [&]( std::initializer_list<const char*> aPhrases )
1000 for(
const char* phrase : aPhrases )
1002 if(
message.find( phrase ) != std::string::npos )
1011 if( aState.starts_with(
"28" )
1012 || mentions( {
"password authentication failed",
"(using password:",
"login failed",
"authentication failed",
1013 "no password supplied" } ) )
1018 if( aState ==
"IM002" || mentions( {
"data source name not found" } ) )
1021 if( aState ==
"IM003" || mentions( {
"can't open lib",
"could not be loaded" } ) )
1024 if( aState.starts_with(
"HYT" ) || mentions( {
"timeout expired",
"timed out" } ) )
1031 if( mentions( {
"connection refused",
"could not connect",
"can't connect",
"unknown host",
1032 "could not translate host name",
"no route to host",
"network is unreachable" } ) )
std::timed_mutex & AccessEngineMutex()
The one lock every user of the Microsoft Access engine takes while the engine initializes or unloads.
std::string AccessEngineBusyMessage()
std::unique_lock< std::timed_mutex > LockAccessEngine(std::chrono::steady_clock::time_point aDeadline)
void MarkAccessWorkerAbandoned()
Records that a worker inside the Access engine was abandoned.
static constexpr size_t ALL_ROWS
Passed as ReadTable()'s row limit to read every row.
static std::chrono::milliseconds CallerBound(int aTimeoutSeconds)
std::function< std::optional< std::string >(const std::string &)> DSN_LOOKUP
Resolves a data source name to its driver, or nullopt if it is unknown.
std::optional< DB_TABLE_INFO > DescribeTable(const std::string &aTable) override
std::map< std::string, std::map< std::string, COLUMN_INFO > > m_columnInfo
Map of table -> map of column name -> column description.
std::optional< std::chrono::steady_clock::time_point > m_engineDeadline
When the wait for the Access driver lock ends, fixed by the first step that finds the lock busy and n...
static std::vector< std::string > ListDrivers()
void Disconnect() override
bool ReadTable(const DB_TABLE_INFO &aTable, const std::vector< std::string > &aColumns, size_t aMaxRows, std::vector< std::string > &aLabels, const DB_ROW_SINK &aSink) override
Reads rows of a table, handing each to aSink.
std::string m_patternEscape
bool takeAccessDriverLock(std::unique_lock< std::timed_mutex > &aLock)
Takes the lock that keeps the Access driver from initializing or unloading on two threads at once.
std::string quoted(const std::string &aIdentifier) const
Wraps an identifier in the driver's quote character, doubling any it contains.
static bool IsAccessDriver(const std::string &aDriverName)
static DB_FAILURE ClassifyFailure(const std::string &aState, const std::string &aMessage)
Classifies a driver error from its SQLSTATE, falling back to its text since drivers disagree on state...
static KEY_PARAMETER DescribeKeyParameter(int aColumnType, long aColumnSize, short aColumnScale, const std::string &aKey)
Chooses the parameter description for a lookup key from its column's cached ODBC description.
void disconnectLocked()
Closes the connection with the driver lock held by the caller.
bool ListTables(std::vector< std::string > &aTables) override
Lists the tables and views, sorted by name.
ODBC_BACKEND(std::string aDsn, std::string aUser, std::string aPass, std::string aConnectionString, long aTimeout)
static bool IsAccessFileInUse(const std::string &aLowerMessage)
std::string m_connectionString
bool IsConnected() const override
static bool NeedsDriverLock(const std::string &aDsn, const std::string &aConnectionString, const DSN_LOOKUP &aLookup)
Decides whether connecting must be serialized because the effective driver is, or may be,...
static SELECTOR FirstSelector(const std::string &aConnectionString)
std::optional< bool > ReadRow(const DB_TABLE_INFO &aTable, const std::vector< std::string > &aColumns, const std::string &aKeyColumn, const std::string &aKey, std::vector< std::string > &aLabels, std::vector< DB_VALUE > &aRow) override
Runs a prepared query whose key parameter is described with the key column's catalog type.
std::string escapePattern(const std::string &aName) const
Escapes catalog search pattern characters so a name matches only itself.
std::unique_ptr< nanodbc::connection > m_conn
bool ListColumns(const std::string &aTable, std::vector< std::string > &aColumns) override
Lists the column names of a table as the catalog reports them, empty if it has none,...
static std::vector< DATA_SOURCE > ListDataSourceDetails()
std::string columnList(const std::vector< std::string > &aColumns) const
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)
std::function< void(std::vector< DB_VALUE > &&)> DB_ROW_SINK
Receives one row at a time, in the order of the labels the read produced.
DB_FAILURE
Why a connection attempt failed, as far as the backend's report tells.
@ TIMEOUT
The source did not answer within the timeout.
@ NO_DSN
The data source name is not configured.
@ LOGIN
The server rejected the user name or password.
@ UNREACHABLE
Nothing answered at the server address, or it refused the connection.
@ FILE_IN_USE
The database file is locked by another process.
@ NO_DRIVER
The driver named by the data source could not be loaded.
std::string NormalizeDecimalText(const std::string &aText)
Trim redundant trailing zeros and a dangling decimal point from decimal text, for example "4700000....
const wxChar *const traceDatabase
Flag to enable database library tracing.
static nanodbc::string fromUTF8(const std::string &aString)
When Unicode support is enabled in nanodbc, string formats are used matching the appropriate characte...
static constexpr unsigned long DEFAULT_KEY_INTEGER_SIZE
static constexpr unsigned long DEFAULT_KEY_DECIMAL_SIZE
static std::vector< RESULT_COLUMN > resultColumns(nanodbc::result &aResults)
Column names and types are the same for every row, so read them once per result set.
static std::vector< DB_VALUE > decodeRow(nanodbc::result &aResults, const std::vector< RESULT_COLUMN > &aColumns)
Decodes the current row of a result set into values in column order.
static std::string toUTF8(const nanodbc::string &aString)
Converts a string from nanodbc-native to KiCad-native.
static constexpr unsigned long DEFAULT_KEY_CHAR_SIZE
static constexpr unsigned long MAX_KEY_CHAR_SIZE
std::string name
The table's name as the backend spells it.
ODBC description of a column, so a lookup key can be bound with the type the driver expects.
short scale
Digits right of the decimal point.
long size
Character length or numeric precision, zero or negative when unknown.
A data source configured in the ODBC driver manager.
How ReadRow describes its key parameter to the driver.
short type
ODBC SQL data type.
int64_t integerValue
The key as a number, set when asInteger is true.
bool asInteger
Bind integerValue as a 64-bit integer instead of the key text.
short scale
Digits right of the decimal point.
unsigned long size
Column size or precision.
The keyword ODBC uses to choose where a connection string connects.
std::vector< DATABASE_CONNECTION::ROW > rows