37#include <system_error>
41#include <fmt/format.h>
58using namespace std::chrono_literals;
64constexpr long adModeRead = 1;
65constexpr long adModeShareDenyNone = 16;
66constexpr long adCmdText = 1;
67constexpr long adSchemaColumns = 4;
68constexpr long adSchemaTables = 20;
71constexpr wchar_t PROG_ID_CURRENT[] =
L"Microsoft.ACE.OLEDB.16.0";
72constexpr wchar_t PROG_ID_FALLBACK[] =
L"Microsoft.ACE.OLEDB.12.0";
75std::wstring toWide(
const std::string& aUtf8 )
77 return std::wstring( wxString::FromUTF8( aUtf8 ).wc_str() );
81std::string toUtf8(
const wchar_t* aWide,
size_t aLength )
83 return std::string( wxString( aWide, aLength ).ToUTF8() );
87std::string quoted(
const std::string& aIdentifier )
90 return "[" + aIdentifier +
"]";
95std::string selectList(
const std::vector<std::string>& aColumns )
97 if( aColumns.empty() )
102 for(
size_t i = 0; i < aColumns.size(); ++i )
103 list += ( i ?
", " :
"" ) + quoted( aColumns[i] );
114 std::string sqlState =
"";
118std::string hresultText( HRESULT aHr )
120 wchar_t* buffer =
nullptr;
121 DWORD length = FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
122 | FORMAT_MESSAGE_IGNORE_INSERTS,
123 nullptr,
static_cast<DWORD
>( aHr ), 0,
reinterpret_cast<LPWSTR
>( &buffer ), 0,
127 if( length && buffer )
128 text = toUtf8( buffer, length );
130 text = fmt::format(
"HRESULT 0x{:08X}",
static_cast<unsigned long>( aHr ) );
135 while( !
text.empty() && (
text.back() ==
'\r' ||
text.back() ==
'\n' ||
text.back() ==
' ' ) )
146 VAR() { VariantInit( &m_v ); }
148 ~VAR() { VariantClear( &m_v ); }
150 VAR(
const VAR& ) =
delete;
151 VAR& operator=(
const VAR& ) =
delete;
153 VAR( VAR&& aOther )
noexcept
156 VariantInit( &aOther.m_v );
159 VAR& operator=( VAR&& aOther )
noexcept
161 if(
this != &aOther )
163 VariantClear( &m_v );
165 VariantInit( &aOther.m_v );
171 static VAR Str(
const std::wstring& aText )
175 V_VT( &v.m_v ) = VT_BSTR;
176 V_BSTR( &v.m_v ) = SysAllocStringLen( aText.data(),
static_cast<UINT
>( aText.size() ) );
180 static VAR Long(
long aValue )
184 V_VT( &v.m_v ) = VT_I4;
185 V_I4( &v.m_v ) = aValue;
194 V_VT( &v.m_v ) = VT_ERROR;
195 V_ERROR( &v.m_v ) = DISP_E_PARAMNOTFOUND;
200 static VAR TableCriteria(
const std::wstring& aTable )
202 SAFEARRAYBOUND bound = { 3, 0 };
203 SAFEARRAY*
array = SafeArrayCreate( VT_VARIANT, 1, &bound );
206 throw ADO_ERROR{ E_OUTOFMEMORY, std::string() };
208 VAR
name = Str( aTable );
212 V_VT( &v.m_v ) = VT_ARRAY | VT_VARIANT;
213 V_ARRAY( &v.m_v ) =
array;
215 const HRESULT hr = SafeArrayPutElement(
array, &
index, &
name.m_v );
218 throw ADO_ERROR{ hr, std::string() };
223 const VARIANT& Raw()
const {
return m_v; }
225 VARIANT* Out() {
return &m_v; }
230 #if defined (__MINGW32__)
231 if(
FAILED( VariantChangeTypeEx( &
copy.m_v,
const_cast<VARIANT*
>(&m_v), LOCALE_INVARIANT, 0, VT_BOOL ) ) )
233 if(
FAILED( VariantChangeTypeEx( &
copy.m_v, &m_v, LOCALE_INVARIANT, 0, VT_BOOL ) ) )
237 return V_BOOL( &
copy.m_v ) != VARIANT_FALSE;
244 #if defined (__MINGW32__)
245 if(
FAILED( VariantChangeTypeEx( &
copy.m_v,
const_cast<VARIANT*
>(&m_v), LOCALE_INVARIANT, 0, VT_I4 ) ) )
247 if(
FAILED( VariantChangeTypeEx( &
copy.m_v, &m_v, LOCALE_INVARIANT, 0, VT_I4 ) ) )
251 return V_I4( &
copy.m_v );
258 #if defined (__MINGW32__)
259 if(
FAILED( VariantChangeTypeEx( &
copy.m_v,
const_cast<VARIANT*
>(&m_v), LOCALE_INVARIANT, 0, VT_BSTR ) ) )
261 if(
FAILED( VariantChangeTypeEx( &
copy.m_v, &m_v, LOCALE_INVARIANT, 0, VT_BSTR ) ) )
263 return std::string();
265 const BSTR
text = V_BSTR( &
copy.m_v );
267 return text ? toUtf8(
text, SysStringLen(
text ) ) : std::string();
270 IDispatch* Dispatch()
const {
return V_VT( &m_v ) == VT_DISPATCH ? V_DISPATCH( &m_v ) : nullptr; }
283 explicit DISP( IDispatch* aOwned ) : m_p( aOwned ) {}
287 DISP(
const DISP& ) =
delete;
288 DISP& operator=(
const DISP& ) =
delete;
290 DISP( DISP&& aOther ) noexcept : m_p( aOther.m_p ), m_ids( std::move( aOther.m_ids ) ) { aOther.m_p =
nullptr; }
292 DISP& operator=( DISP&& aOther )
noexcept
294 if(
this != &aOther )
298 m_ids = std::move( aOther.m_ids );
299 aOther.m_p =
nullptr;
305 static DISP Create(
const wchar_t* aProgId )
308 HRESULT hr = CLSIDFromProgID( aProgId, &clsid );
311 throw ADO_ERROR{ hr, std::string() };
313 IDispatch*
object =
nullptr;
315 hr = CoCreateInstance( clsid,
nullptr, CLSCTX_INPROC_SERVER, IID_IDispatch,
316 reinterpret_cast<void**
>( &
object ) );
319 throw ADO_ERROR{ hr, std::string() };
321 return DISP(
object );
325 static DISP From(
const VAR& aValue )
327 IDispatch*
object = aValue.Dispatch();
330 throw ADO_ERROR{ E_POINTER, std::string() };
333 return DISP(
object );
348 template <
typename... ARGS>
349 VAR Property(
const wchar_t* aName,
const ARGS&... aArgs )
351 return invoke( aName, DISPATCH_PROPERTYGET, { aArgs.Raw()... } );
354 void SetProperty(
const wchar_t* aName,
const VAR& aValue )
356 invoke( aName, DISPATCH_PROPERTYPUT, { aValue.Raw() } );
359 template <
typename... ARGS>
360 VAR Call(
const wchar_t* aName,
const ARGS&... aArgs )
362 return invoke( aName, DISPATCH_METHOD, { aArgs.Raw()... } );
366 VAR invoke(
const wchar_t* aName, WORD aFlags, std::vector<VARIANT> aArgs )
368 auto found = m_ids.find( aName );
370 if( found == m_ids.end() )
373 LPOLESTR
name =
const_cast<LPOLESTR
>( aName );
374 HRESULT hr = m_p->GetIDsOfNames( IID_NULL, &
name, 1, LOCALE_USER_DEFAULT, &
id );
377 throw ADO_ERROR{ hr, std::string() };
379 found = m_ids.emplace( aName,
id ).first;
383 std::vector<VARIANT> raw( aArgs.rbegin(), aArgs.rend() );
385 DISPPARAMS params = {};
386 DISPID putId = DISPID_PROPERTYPUT;
388 params.cArgs =
static_cast<UINT
>( raw.size() );
389 params.rgvarg = raw.empty() ? nullptr : raw.data();
391 if( aFlags == DISPATCH_PROPERTYPUT )
393 params.cNamedArgs = 1;
394 params.rgdispidNamedArgs = &putId;
400 HRESULT hr = m_p->Invoke( found->second, IID_NULL, LOCALE_USER_DEFAULT, aFlags, ¶ms,
result.Out(),
405 if( hr == DISP_E_EXCEPTION &&
info.pfnDeferredFillIn )
408 ADO_ERROR error{ hr == DISP_E_EXCEPTION &&
FAILED(
info.scode ) ?
info.scode : hr, std::string() };
410 if(
info.bstrDescription )
411 error.text = toUtf8(
info.bstrDescription, SysStringLen(
info.bstrDescription ) );
413 SysFreeString(
info.bstrSource );
414 SysFreeString(
info.bstrDescription );
415 SysFreeString(
info.bstrHelpFile );
422 IDispatch* m_p =
nullptr;
423 std::map<std::wstring, DISPID> m_ids;
428void clearProviderErrors( DISP& aConnection )
432 DISP errors = DISP::From( aConnection.Property(
L"Errors" ) );
434 errors.Call(
L"Clear" );
436 catch(
const ADO_ERROR& )
444void applyProviderErrors( DISP& aConnection, ADO_ERROR& aError )
447 std::string sqlState;
451 DISP errors = DISP::From( aConnection.Property(
L"Errors" ) );
452 long count = errors.Property(
L"Count" ).AsLong();
454 for(
long i = 0; i < count; ++i )
456 DISP error = DISP::From( errors.Property(
L"Item", VAR::Long( i ) ) );
457 std::string description = error.Property(
L"Description" ).AsString();
459 if( sqlState.empty() )
460 sqlState = error.Property(
L"SQLState" ).AsString();
462 if( description.empty() ||
text.find( description ) != std::string::npos )
465 text += (
text.empty() ?
"" :
"; " ) + description;
468 catch(
const ADO_ERROR& )
472 clearProviderErrors( aConnection );
477 aError.sqlState = sqlState;
487 bool engineBusy =
false;
491CALL_RESULT failed(
DB_FAILURE aFailure, std::string aDetail )
493 return {
false, aFailure, std::move( aDetail ) };
501 std::vector<std::string> names;
504 std::vector<DB_COLUMN> columns;
508bool isNetworkError( DWORD aCode )
512 case ERROR_BAD_NETPATH:
513 case ERROR_BAD_NET_NAME:
514 case ERROR_NETNAME_DELETED:
515 case ERROR_UNEXP_NET_ERR:
516 case ERROR_NETWORK_BUSY:
517 case ERROR_NETWORK_UNREACHABLE:
518 case ERROR_HOST_UNREACHABLE:
519 case ERROR_CONNECTION_UNAVAIL:
520 case ERROR_NO_NETWORK:
521 case ERROR_REM_NOT_LIST:
522 case ERROR_DEV_NOT_EXIST:
523 case ERROR_SEM_TIMEOUT:
524 case ERROR_NOT_CONNECTED:
534bool isProviderMissing( HRESULT aHr )
536 constexpr HRESULT ADO_PROVIDER_NOT_FOUND =
static_cast<HRESULT
>( 0x800A0E7A );
538 return aHr == ADO_PROVIDER_NOT_FOUND || aHr == REGDB_E_CLASSNOTREG || aHr == CO_E_CLASSSTRING
539 || aHr == HRESULT_FROM_WIN32( ERROR_MOD_NOT_FOUND );
544std::optional<DB_FAILURE> classifyByNumber(
const std::string& aSqlState )
547 auto [
end, code] = std::from_chars( aSqlState.data(), aSqlState.data() + aSqlState.size(), number );
549 if( code != std::errc() ||
end != aSqlState.data() + aSqlState.size() )
578const FIXED_MESSAGE FIXED_MESSAGES[] = {
587DB_FAILURE classifyFailure(
const ADO_ERROR& aError,
const std::string& aText )
589 if( std::optional<DB_FAILURE> byNumber = classifyByNumber( aError.sqlState ) )
592 if( isProviderMissing( aError.hr ) )
595 if( HRESULT_FACILITY( aError.hr ) == FACILITY_WIN32 && isNetworkError( HRESULT_CODE( aError.hr ) ) )
598 std::wstring lower = toWide( aText );
600 std::transform( lower.begin(), lower.end(), lower.begin(),
603 return static_cast<wchar_t>( std::towlower( c ) );
606 for(
const FIXED_MESSAGE&
message : FIXED_MESSAGES )
625std::map<uint64_t, ADO_SESSION>& sessions()
627 thread_local std::map<uint64_t, ADO_SESSION> s_sessions;
631std::atomic<uint64_t> g_nextSession{ 1 };
634ADO_SESSION& sessionFor( uint64_t aSession )
636 auto it = sessions().find( aSession );
638 if( it == sessions().
end() )
639 throw ADO_ERROR{ HRESULT_FROM_WIN32( ERROR_CONNECTION_UNAVAIL ),
"the connection to the database was lost" };
647template <
typename BODY>
648CALL_RESULT guarded( BODY aBody, uint64_t aSession = 0 )
652 auto opened = sessions().find( aSession );
654 if( opened != sessions().
end() )
655 clearProviderErrors( opened->second.connection );
659 catch( ADO_ERROR& error )
661 auto session = sessions().find( aSession );
663 if( error.sqlState.empty() && session != sessions().
end() )
664 applyProviderErrors( session->second.connection, error );
666 const std::string
text = error.text.empty() ? hresultText( error.hr ) : error.
text;
669 wxLogTrace(
traceDatabase, wxT(
"ACE_BACKEND: call failed (0x%08lx, %s) as failure %d" ),
670 static_cast<unsigned long>( error.hr ), wxString::FromUTF8( error.sqlState ),
671 static_cast<int>( failure ) );
673 return failed( failure,
text );
675 catch(
const std::exception& error )
684 return SUCCEEDED( CoInitializeEx(
nullptr, COINIT_APARTMENTTHREADED ) );
689thread_local std::shared_ptr<ACE_ENGINE_OP> s_threadOp;
693std::unique_lock<std::timed_mutex> lockEngine(
ACE_ENGINE_OP& aOp )
709std::chrono::milliseconds remainingUntil( ACE_ENGINE_OP::CLOCK::time_point aDeadline )
711 return std::max( std::chrono::milliseconds( 0 ),
712 std::chrono::ceil<std::chrono::milliseconds>( aDeadline - ACE_ENGINE_OP::CLOCK::now() ) );
717thread_local bool s_leftSessionOpen =
false;
723 const ACE_ENGINE_OP::CLOCK::time_point deadline =
724 s_threadOp ? s_threadOp->deadline : ACE_ENGINE_OP::CLOCK::time_point();
729 if( !lock.owns_lock() || s_leftSessionOpen )
733 new std::map<uint64_t, ADO_SESSION>( std::move( sessions() ) );
744 auto it = sessions().find( aSession );
746 if( it == sessions().
end() )
749 std::unique_lock lock = lockEngine( aOp );
751 if( !lock.owns_lock() )
755 new ADO_SESSION( std::move( it->second ) );
756 sessions().erase( it );
757 s_leftSessionOpen =
true;
763 it->second.connection.Call(
L"Close" );
765 catch(
const ADO_ERROR& )
769 sessions().erase( it );
784constexpr std::chrono::seconds PROBE_WAIT{ 10 };
788PROBE_ANSWER findProvider()
795 std::unique_lock lock =
LockAccessEngine( std::chrono::steady_clock::now() + PROBE_WAIT );
797 if( !lock.owns_lock() )
803 if( !comThreadInit() )
805 found.error =
"COM could not be initialized";
811 ~COM_SCOPE() { CoUninitialize(); }
814 for(
const wchar_t* candidate : { PROG_ID_CURRENT, PROG_ID_FALLBACK } )
818 if(
FAILED( CLSIDFromProgID( candidate, &clsid ) ) )
821 IUnknown* provider =
nullptr;
823 if( SUCCEEDED( CoCreateInstance( clsid,
nullptr, CLSCTX_INPROC_SERVER, IID_IUnknown,
824 reinterpret_cast<void**
>( &provider ) ) ) )
827 found.progId = candidate;
832 catch(
const std::exception& error )
834 found.progId.clear();
835 found.error = error.what();
836 wxLogTrace(
traceDatabase, wxT(
"ACE_BACKEND: provider probe failed: %s" ), wxString::FromUTF8( found.error ) );
844std::optional<PROBE_ANSWER> probeAnswer( std::chrono::milliseconds aWait )
846 static auto* s_mutex =
new std::mutex;
854 std::lock_guard lock( *s_mutex );
858 std::optional<PROBE_ANSWER> answer = probe->
Get( aWait );
861 if( answer && answer->busy )
863 std::lock_guard lock( *s_mutex );
865 if( s_probe == probe )
876 std::optional<PROBE_ANSWER> found = probeAnswer( aWait );
884 aProgId = found->progId;
890std::wstring universalPath(
const std::wstring& aPath )
892 if( aPath.size() < 2 || aPath[1] !=
L':' )
896 std::vector<BYTE> buffer( size );
897 DWORD code = WNetGetUniversalNameW( aPath.c_str(), UNIVERSAL_NAME_INFO_LEVEL, buffer.data(), &size );
899 if( code == ERROR_MORE_DATA )
901 buffer.resize( size );
902 code = WNetGetUniversalNameW( aPath.c_str(), UNIVERSAL_NAME_INFO_LEVEL, buffer.data(), &size );
905 if( code != NO_ERROR )
908 return reinterpret_cast<UNIVERSAL_NAME_INFOW*
>( buffer.data() )->lpUniversalName;
913std::wstring quotedValue(
const std::wstring& aValue )
917 for(
wchar_t c : aValue )
929CALL_RESULT connectJob( uint64_t aSession,
const std::wstring& aProgId,
const std::string& aPath,
930 const std::string& aPassword,
int aTimeout,
ACE_ENGINE_OP& aOp )
935 const std::wstring
path = universalPath( toWide( aPath ) );
937 if( GetFileAttributesW(
path.c_str() ) == INVALID_FILE_ATTRIBUTES )
939 const DWORD code = GetLastError();
941 if( code == ERROR_FILE_NOT_FOUND || code == ERROR_PATH_NOT_FOUND )
944 if( isNetworkError( code ) )
948 std::wstring connectionString =
L"Provider=" + aProgId +
L";Data Source=" + quotedValue(
path ) +
L";";
950 if( !aPassword.empty() )
952 connectionString +=
L"Jet OLEDB:Database Password=" + quotedValue( toWide( aPassword ) ) +
L";";
956 std::unique_lock lock = lockEngine( aOp );
958 if( !lock.owns_lock() )
961 busy.engineBusy =
true;
965 DISP connection = DISP::Create(
L"ADODB.Connection" );
967 connection.SetProperty(
L"Mode", VAR::Long( adModeRead | adModeShareDenyNone ) );
971 connection.SetProperty(
L"ConnectionTimeout", VAR::Long( aTimeout ) );
972 connection.SetProperty(
L"CommandTimeout", VAR::Long( aTimeout ) );
977 connection.Call(
L"Open", VAR::Str( connectionString ) );
979 catch( ADO_ERROR& error )
981 applyProviderErrors( connection, error );
985 sessions()[aSession].connection = std::move( connection );
996void checkBatch( HRESULT aHr )
999 throw ADO_ERROR{ aHr,
"invalid row batch" };
1004template <
typename ON_ROW>
1005void forEachRow( DISP& aRecordset, ON_ROW aOnRow )
1009 if( aRecordset.Property(
L"EOF" ).AsBool() )
1012 VAR batch = aRecordset.Call(
L"GetRows", VAR::Long( GET_ROWS_BATCH ) );
1015 if( V_VT( &batch.Raw() ) != ( VT_ARRAY | VT_VARIANT ) || !V_ARRAY( &batch.Raw() ) )
1016 checkBatch( E_FAIL );
1018 SAFEARRAY*
array = V_ARRAY( &batch.Raw() );
1019 LONG fieldLower = 0;
1020 LONG fieldUpper = -1;
1024 if( SafeArrayGetDim(
array ) != 2 )
1025 checkBatch( E_FAIL );
1027 checkBatch( SafeArrayGetLBound(
array, 1, &fieldLower ) );
1028 checkBatch( SafeArrayGetUBound(
array, 1, &fieldUpper ) );
1029 checkBatch( SafeArrayGetLBound(
array, 2, &rowLower ) );
1030 checkBatch( SafeArrayGetUBound(
array, 2, &rowUpper ) );
1032 const size_t fields =
static_cast<size_t>( fieldUpper - fieldLower + 1 );
1033 const size_t rows =
static_cast<size_t>( rowUpper - rowLower + 1 );
1037 checkBatch( SafeArrayAccessData(
array,
reinterpret_cast<void**
>( &cells ) ) );
1044 for(
size_t row = 0; row <
rows && more; ++row )
1046 std::vector<DB_VALUE> values;
1047 values.reserve( fields );
1049 for(
size_t field = 0; field < fields; ++field )
1050 values.push_back( convertValue( cells[row * fields + field] ) );
1052 more = aOnRow( std::move( values ) );
1057 SafeArrayUnaccessData(
array );
1061 SafeArrayUnaccessData(
array );
1063 if( !more ||
rows == 0 )
1069std::string bstrText( BSTR aText )
1071 return aText ? toUtf8( aText, SysStringLen( aText ) ) :
std::string();
1080 if( V_VT( &value ) & VT_BYREF )
1084 if(
FAILED( VariantCopyInd(
copy.Out(), &value ) ) )
1085 return std::monostate();
1087 return convertValue(
copy.Raw() );
1090 switch( V_VT( &value ) )
1095 return std::monostate();
1098 return bstrText( V_BSTR( &value ) );
1101 return static_cast<int64_t
>( V_I1( &value ) );
1104 return static_cast<int64_t
>( V_UI1( &value ) );
1107 return static_cast<int64_t
>( V_I2( &value ) );
1110 return static_cast<int64_t
>( V_UI2( &value ) );
1114 return static_cast<int64_t
>( V_I4( &value ) );
1118 return static_cast<int64_t
>( V_UI4( &value ) );
1121 return static_cast<int64_t
>( V_I8( &value ) );
1124 if( V_UI8( &value ) >
static_cast<ULONGLONG
>( INT64_MAX ) )
1125 return std::to_string( V_UI8( &value ) );
1127 return static_cast<int64_t
>( V_UI8( &value ) );
1132 return V_R4( &value ) == 0.0f ? std::string(
"0" ) : fmt::format(
"{}", V_R4( &value ) );
1135 return V_R8( &value );
1138 return V_BOOL( &value ) != VARIANT_FALSE;
1144 if( !VariantTimeToSystemTime( V_DATE( &value ), &time ) )
1145 return std::monostate();
1147 return fmt::format(
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}", time.wYear, time.wMonth, time.wDay, time.wHour,
1148 time.wMinute, time.wSecond );
1155 HRESULT hr = V_VT( &value ) == VT_CY ? VarBstrFromCy( V_CY( &value ), LOCALE_INVARIANT, 0, &raw )
1156 : VarBstrFromDec( &V_DECIMAL( &value ), LOCALE_INVARIANT, 0, &raw );
1159 return std::monostate();
1161 std::unique_ptr<OLECHAR,
decltype( &SysFreeString )>
text( raw, &SysFreeString );
1166 case VT_ARRAY | VT_UI1:
1167 return std::monostate();
1173 if(
FAILED( VariantChangeTypeEx(
text.Out(), &value, LOCALE_INVARIANT, 0, VT_BSTR ) ) )
1174 return std::monostate();
1176 return bstrText( V_BSTR( &
text.Raw() ) );
1183long fieldIndex( DISP& aRecordset,
const wchar_t* aName )
1185 DISP fields = DISP::From( aRecordset.Property(
L"Fields" ) );
1186 long count = fields.Property(
L"Count" ).AsLong();
1188 for(
long i = 0; i < count; ++i )
1190 DISP field = DISP::From( fields.Property(
L"Item", VAR::Long( i ) ) );
1192 if( _wcsicmp( toWide( field.Property(
L"Name" ).AsString() ).c_str(), aName ) == 0 )
1196 throw ADO_ERROR{ E_FAIL,
"the provider's catalog lacks the column " + toUtf8( aName, wcslen( aName ) ) };
1201int64_t asInteger(
const DB_VALUE& aValue )
1203 if(
const int64_t* integer = std::get_if<int64_t>( &aValue ) )
1206 if(
const double* real = std::get_if<double>( &aValue ) )
1207 return static_cast<int64_t
>( *real );
1209 if(
const std::string*
text = std::get_if<std::string>( &aValue ) )
1210 return std::strtoll(
text->c_str(),
nullptr, 10 );
1217void describeType(
long aOleDbType,
int& aOdbcType,
bool& aSupported )
1220 enum OLE_DB_TYPE :
long
1229 DBTYPE_IDISPATCH = 9,
1231 DBTYPE_UNKNOWN = 13,
1232 DBTYPE_DECIMAL = 14,
1239 DBTYPE_NUMERIC = 131,
1241 DBTYPE_DBDATE = 133,
1242 DBTYPE_DBTIME = 134,
1243 DBTYPE_DBTIMESTAMP = 135,
1244 DBTYPE_CHAPTER = 136,
1245 DBTYPE_VARIANT = 138
1248 enum ODBC_SQL_TYPE :
int
1251 ODBC_SQL_NUMERIC = 2,
1252 ODBC_SQL_DECIMAL = 3,
1253 ODBC_SQL_INTEGER = 4,
1254 ODBC_SQL_SMALLINT = 5,
1256 ODBC_SQL_DOUBLE = 8,
1257 ODBC_SQL_VARCHAR = 12,
1258 ODBC_SQL_TYPE_TIMESTAMP = 93,
1259 ODBC_SQL_BIGINT = -5,
1260 ODBC_SQL_TINYINT = -6,
1262 ODBC_SQL_BINARY = -3,
1267 aOdbcType = ODBC_SQL_VARCHAR;
1269 switch( aOleDbType )
1271 case DBTYPE_I2: aOdbcType = ODBC_SQL_SMALLINT;
break;
1272 case DBTYPE_I4: aOdbcType = ODBC_SQL_INTEGER;
break;
1273 case DBTYPE_R4: aOdbcType = ODBC_SQL_REAL;
break;
1274 case DBTYPE_R8: aOdbcType = ODBC_SQL_DOUBLE;
break;
1275 case DBTYPE_CY: aOdbcType = ODBC_SQL_DECIMAL;
break;
1276 case DBTYPE_BOOL: aOdbcType = ODBC_SQL_BIT;
break;
1277 case DBTYPE_DECIMAL: aOdbcType = ODBC_SQL_DECIMAL;
break;
1278 case DBTYPE_UI1: aOdbcType = ODBC_SQL_TINYINT;
break;
1279 case DBTYPE_I8: aOdbcType = ODBC_SQL_BIGINT;
break;
1280 case DBTYPE_GUID: aOdbcType = ODBC_SQL_GUID;
break;
1281 case DBTYPE_NUMERIC: aOdbcType = ODBC_SQL_NUMERIC;
break;
1286 case DBTYPE_DBTIMESTAMP: aOdbcType = ODBC_SQL_TYPE_TIMESTAMP;
break;
1290 case DBTYPE_WSTR: aOdbcType = ODBC_SQL_VARCHAR;
break;
1294 aOdbcType = ODBC_SQL_BINARY;
1298 case DBTYPE_IDISPATCH:
1299 case DBTYPE_UNKNOWN:
1301 case DBTYPE_CHAPTER:
1302 case DBTYPE_VARIANT:
1303 aOdbcType = ODBC_SQL_NONE;
1312CATALOG_RESULT listTablesJob( uint64_t aSession )
1317 [&]() -> CALL_RESULT
1319 ADO_SESSION& session = sessionFor( aSession );
1321 DISP::From( session.connection.Call(
L"OpenSchema", VAR::Long( adSchemaTables ) ) );
1322 const long nameIndex = fieldIndex( schema,
L"TABLE_NAME" );
1323 const long typeIndex = fieldIndex( schema,
L"TABLE_TYPE" );
1326 [&]( std::vector<DB_VALUE>&& aRow )
1332 if( ( type ==
"TABLE" || type ==
"VIEW" ) &&
name.rfind(
"MSys", 0 ) != 0
1333 &&
name.rfind(
"~", 0 ) != 0 )
1341 schema.Call(
L"Close" );
1350CATALOG_RESULT describeTableJob( uint64_t aSession,
const std::string& aTable )
1355 [&]() -> CALL_RESULT
1357 ADO_SESSION& session = sessionFor( aSession );
1358 DISP schema = DISP::From( session.connection.Call(
1359 L"OpenSchema", VAR::Long( adSchemaColumns ), VAR::TableCriteria( toWide( aTable ) ) ) );
1360 const long tableIndex = fieldIndex( schema,
L"TABLE_NAME" );
1361 const long nameIndex = fieldIndex( schema,
L"COLUMN_NAME" );
1362 const long positionIndex = fieldIndex( schema,
L"ORDINAL_POSITION" );
1363 const long typeIndex = fieldIndex( schema,
L"DATA_TYPE" );
1365 std::vector<std::pair<int64_t, DB_COLUMN>> columns;
1368 [&]( std::vector<DB_VALUE>&& aRow )
1373 describeType(
static_cast<long>( asInteger( aRow[typeIndex] ) ), column.
type,
1382 columns.emplace_back( asInteger( aRow[positionIndex] ), std::move( column ) );
1386 schema.Call(
L"Close" );
1388 std::stable_sort( columns.begin(), columns.end(),
1389 [](
const auto& aLeft,
const auto& aRight )
1391 return aLeft.first < aRight.first;
1394 for(
auto& column : columns )
1395 result.columns.push_back( std::move( column.second ) );
1406DISP executeQuery( ADO_SESSION& aSession,
const std::string& aSql )
1410 return DISP::From( aSession.connection.Call(
L"Execute", VAR::Str( toWide( aSql ) ), VAR::Missing(),
1411 VAR::Long( adCmdText ) ) );
1413 catch( ADO_ERROR& error )
1415 applyProviderErrors( aSession.connection, error );
1427 const uint8_t tag =
static_cast<uint8_t
>( aValue.index() );
1428 const uint8_t present = std::holds_alternative<std::monostate>( aValue ) ? 0 : 1;
1430 add( &tag,
sizeof( tag ) );
1431 add( &present,
sizeof( present ) );
1434 [
this](
const auto& aCell )
1441 uint64_t Value()
const {
return m_hash; }
1444 void payload( std::monostate ) { length( 0 ); }
1446 void payload(
const std::string& aText )
1448 length( aText.size() );
1449 add( aText.data(), aText.size() );
1452 void payload( int64_t aValue )
1454 length(
sizeof( aValue ) );
1455 add( &aValue,
sizeof( aValue ) );
1458 void payload(
double aValue )
1460 length(
sizeof( aValue ) );
1461 add( &aValue,
sizeof( aValue ) );
1464 void payload(
bool aValue )
1466 const uint8_t
byte = aValue ? 1 : 0;
1468 length(
sizeof(
byte ) );
1469 add( &
byte,
sizeof(
byte ) );
1472 void length( uint64_t aLength ) { add( &aLength,
sizeof( aLength ) ); }
1474 void add(
const void* aData,
size_t aSize )
1476 const uint8_t*
bytes =
static_cast<const uint8_t*
>( aData );
1478 for(
size_t i = 0; i < aSize; ++i )
1481 m_hash *= 0x100000001b3ULL;
1485 uint64_t m_hash = 0xcbf29ce484222325ULL;
1499PASS_RESULT readTableJob(
DB_JOB_CONTEXT& aContext, uint64_t aSession,
const std::string& aSql,
1500 const std::string& aUnorderedSql, std::shared_ptr<std::vector<std::string>> aLabels,
1506 pass.call = guarded(
1507 [&]() -> CALL_RESULT
1509 ADO_SESSION& session = sessionFor( aSession );
1514 recordset = executeQuery( session, aSql );
1516 catch(
const ADO_ERROR& error )
1519 if( aUnorderedSql.empty() || classifyFailure( error, error.text ) !=
DB_FAILURE::UNKNOWN )
1522 recordset = executeQuery( session, aUnorderedSql );
1526 DISP fields = DISP::From( recordset.Property(
L"Fields" ) );
1527 const long count = fields.Property(
L"Count" ).AsLong();
1529 for(
long i = 0; i < count; ++i )
1531 DISP field = DISP::From( fields.Property(
L"Item", VAR::Long( i ) ) );
1533 aLabels->push_back( field.Property(
L"Name" ).AsString() );
1537 bool accepted =
true;
1538 size_t delivered = 0;
1542 forEachRow( recordset,
1543 [&]( std::vector<DB_VALUE>&& aRow )
1546 if( delivered >= aMaxRows )
1551 for(
const DB_VALUE& value : aRow )
1554 accepted = aContext.
Emit( std::move( aRow ) );
1559 if( !accepted || !aContext.
Flush() )
1564 recordset.Call(
L"Close" );
1566 catch( ADO_ERROR& error )
1568 applyProviderErrors( session.connection, error );
1574 wxLogTrace(
traceDatabase, wxT(
"ACE_BACKEND: closing the recordset failed (0x%08lx, %s)" ),
1575 static_cast<unsigned long>( error.hr ), wxString::FromUTF8( error.sqlState ) );
1578 pass.rows = delivered;
1583 pass.hash = hash.Value();
1612 m_worker->SetShutdownBound( remainingUntil( op->deadline ) );
1616 wxLogTrace(
traceDatabase, wxT(
"ACE_BACKEND: %s is blocked in provider I/O; its thread is detached and "
1617 "finishes when the share answers" ),
1618 wxString::FromUTF8(
m_path ) );
1625 std::wstring progId;
1627 return providerLookup( progId, 0ms );
1633 return utf8(
_(
"still checking for the Microsoft Access Database Engine; try again in a moment" ) );
1639 std::string
message =
utf8(
_(
"the Microsoft Access Database Engine (64-bit) is not installed; download it from "
1640 "https://www.microsoft.com/en-us/download/details.aspx?id=54920" ) );
1642 std::optional<PROBE_ANSWER> answer = probeAnswer( 0ms );
1644 if( answer && !answer->error.empty() )
1645 message +=
" (" + answer->error +
")";
1662 const std::string& aPassword )
1664 if( aResult.engineBusy )
1667 const wxString
path = wxString::FromUTF8( aPath );
1670 const std::string redacted =
RedactDetail( aResult.detail, aPassword );
1671 const wxString suffix =
1672 redacted.empty() ? wxString() : wxString::Format( wxT(
" (%s)" ), wxString::FromUTF8( redacted ) );
1675 switch( aResult.failure )
1681 message = wxString::Format(
_(
"database file %s is locked by another user" ),
path );
1685 message = wxString::Format(
_(
"the password for database file %s is missing or wrong" ),
path );
1689 message = wxString::Format(
_(
"database file %s could not be reached" ),
path );
1692 default:
message = wxString::Format(
_(
"database file %s could not be read" ),
path );
break;
1708 fail( aFailure, std::move( aMessage ) );
1725template <
typename R>
1727 std::shared_ptr<ACE_ENGINE_OP> aOp,
1728 std::optional<std::chrono::milliseconds> aProgressBound )
1735 auto wrapped = [op = aOp, job = std::move( aJob )](
DB_JOB_CONTEXT& aContext )
1738 return job( aContext );
1741 const std::chrono::milliseconds bound = aProgressBound ? *aProgressBound : remainingUntil( aOp->deadline );
1742 std::optional<R>
result =
m_worker->Run<R>( std::move( wrapped ), bound, aSink );
1767 return std::nullopt;
1773 std::vector<uint64_t> ids;
1779 ids.push_back( cycleSession.id );
1787 if( !
m_worker->HasLiveThread() || ( ids.empty() && !aForExit ) )
1798 std::optional<std::chrono::milliseconds> aBound )
1800 const bool closed =
m_worker->RunCleanup(
1805 for( uint64_t
id : ids )
1806 closeSession(
id, *aOp );
1808 aBound ? *aBound : remainingUntil( aOp->deadline ) );
1833 std::wstring progId;
1835 switch( providerLookup( progId, PROBE_WAIT ) )
1851 const uint64_t session = g_nextSession++;
1859 return connectJob( session, progId,
path, password, timeout, *op );
1898 return listTablesJob( session );
1911 aTables = std::move(
result->names );
1912 std::sort( aTables.begin(), aTables.end() );
1924 return std::nullopt;
1931 return describeTableJob( session, aTable );
1936 return std::nullopt;
1941 return std::nullopt;
1945 return std::nullopt;
1957 std::vector<std::string>& aLabels,
const DB_ROW_SINK& aSink )
1965 const std::string columns = selectList( aColumns );
1966 const bool bounded = aMaxRows !=
ALL_ROWS;
1967 const std::string from =
" FROM " + quoted( aTable.
name );
1968 std::string sql =
"SELECT ";
1969 std::string unordered;
1973 sql += columns + from +
" WHERE 1=0";
1977 const std::string
top =
"TOP " + std::to_string( aMaxRows ) +
" ";
1979 unordered = sql +
top + columns + from;
1980 sql +=
top + columns + from +
" ORDER BY 1";
1984 sql += columns + from;
1987 return readPass(
m_session, sql, unordered, aMaxRows, aLabels, &aSink ).has_value();
1991std::optional<ACE_BACKEND::PASS_DIGEST>
1993 std::vector<std::string>& aLabels,
const DB_ROW_SINK* aSink,
1994 std::optional<ACE_ENGINE_OP::CLOCK::time_point> aDeadline )
1996 static const DB_ROW_SINK discard = []( std::vector<DB_VALUE>&& ) {};
1999 auto labels = delivery.
Labels();
2001 std::shared_ptr<ACE_ENGINE_OP> op;
2002 std::optional<std::chrono::milliseconds> progressBound;
2007 op = std::make_shared<ACE_ENGINE_OP>( *aDeadline );
2012 [aSession, aSql, aUnorderedSql, labels, aMaxRows](
DB_JOB_CONTEXT& aContext )
2014 return readTableJob( aContext, aSession, aSql, aUnorderedSql, labels, aMaxRows );
2016 delivery.
Sink(), op, progressBound );
2019 return std::nullopt;
2032 return std::nullopt;
2054 std::vector<uint64_t> ids;
2057 ids.push_back( cycleSession.id );
2084 utf8( wxString::Format(
_(
"the read of table %s was stopped" ), wxString::FromUTF8( aTable ) ) ) );
2090 const ACE_ENGINE_OP::CLOCK::time_point until = ACE_ENGINE_OP::CLOCK::now() + aWait;
2095 const ACE_ENGINE_OP::CLOCK::duration
left = until - ACE_ENGINE_OP::CLOCK::now();
2097 if(
left <= ACE_ENGINE_OP::CLOCK::duration::zero() )
2100 std::this_thread::sleep_for( std::min<ACE_ENGINE_OP::CLOCK::duration>(
left, 50ms ) );
2114 std::vector<uint64_t> ids;
2117 ids.push_back( it->id );
2122 if(
closeOnWorker( std::move( ids ), std::make_shared<ACE_ENGINE_OP>( aDeadline ),
2145 ACE_ENGINE_OP::CLOCK::time_point aDeadline,
2146 const std::function<
bool()>& aMayStart )
2150 if( cycleSession.tables.insert( aTable ).second )
2151 return cycleSession.id;
2155 return std::nullopt;
2157 std::minstd_rand jitter( std::random_device{}() );
2159 for(
int attempt = 0;; ++attempt )
2163 return std::nullopt;
2165 const uint64_t session = g_nextSession++;
2166 auto op = std::make_shared<ACE_ENGINE_OP>( aDeadline );
2172 return connectJob( session, progId,
path, password, timeout, *op );
2177 return std::nullopt;
2183 wxLogTrace(
traceDatabase, wxT(
"ACE_BACKEND: session %llu opened for %s, %zu held by the cycle" ),
2184 static_cast<unsigned long long>( session ), wxString::FromUTF8( aTable ),
2189 const std::chrono::milliseconds wait =
FileInUseBackoff( attempt, jitter );
2194 return std::nullopt;
2203 std::vector<std::string>& aLabels,
const DB_ROW_SINK& aSink,
2214 const bool read =
readVerified( aTable, aColumns, aLabels, aSink, aRestart );
2234 std::vector<std::string>& aLabels,
const DB_ROW_SINK& aSink,
2239 const std::string sql =
"SELECT " + selectList( aColumns ) +
" FROM " + quoted( aTable.
name );
2240 const auto start = CLOCK::now();
2242 const wxString
table = wxString::FromUTF8( aTable.
name );
2243 const wxString
path = wxString::FromUTF8(
m_path );
2246 std::optional<CLOCK::time_point> retryDeadline;
2248 std::minstd_rand random( std::random_device{}() );
2249 std::uniform_int_distribution<int> pauseMs( 50, 250 );
2261 if( !retryDeadline || CLOCK::now() < *retryDeadline )
2265 utf8( wxString::Format(
_(
"table %s in database file %s is being modified by another user" ),
2274 return std::optional<PASS_DIGEST>();
2276 const auto passStart = CLOCK::now();
2279 const CLOCK::time_point lockDeadline = retryDeadline ? *retryDeadline : passStart + bound;
2281 std::optional<uint64_t> session =
freshSession( aTable.
name, lockDeadline, mayStart );
2283 if( !session || !mayStart() )
2284 return std::optional<PASS_DIGEST>();
2286 const auto readStart = CLOCK::now();
2287 std::optional<PASS_DIGEST> digest =
2288 readPass( *session, sql, std::string(),
ALL_ROWS, aLabels, aPassSink, lockDeadline );
2292 using MS = std::chrono::duration<double, std::milli>;
2295 wxT(
"ACE_BACKEND: pass %zu of %s on session %llu: %zu rows, hash %016llx, "
2296 "open %0.1f ms, read %0.1f ms" ),
2297 aPass,
table,
static_cast<unsigned long long>( *session ), digest->rows,
2298 static_cast<unsigned long long>( digest->hash ),
MS( readStart - passStart ).count(),
2299 MS( CLOCK::now() - readStart ).count() );
2305 std::optional<PASS_DIGEST> previous = timedPass( 0,
nullptr );
2310 for(
size_t pass = 1;; ++pass )
2323 std::optional<PASS_DIGEST> current = timedPass( pass, &aSink );
2328 if( !retryDeadline )
2329 retryDeadline = CLOCK::now() + bound;
2331 if( *current == *previous )
2333 wxLogTrace(
traceDatabase, wxT(
"ACE_BACKEND: %s verified after %zu passes in %0.1f ms" ),
table,
2334 pass + 1, std::chrono::duration<double, std::milli>( CLOCK::now() - start ).count() );
2338 previous = std::move( current );
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 std::string describeFailure(const std::string &aPath, const CALL_RESULT &aResult, const std::string &aPassword)
Composes the message for a failed call, naming the file.
static bool invalidates(DB_FAILURE aFailure)
A failure after which the connection can no longer be trusted.
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.
void dropConnection(std::shared_ptr< ACE_ENGINE_OP > aOp=nullptr, bool aForExit=false)
Forgets the connection and the cycle's sessions and closes them on the worker thread,...
std::shared_ptr< ACE_ENGINE_OP > newOp() const
ACE_BACKEND(std::string aPath, std::string aPassword, int aTimeoutSeconds)
std::unique_ptr< DB_WORKER_THREAD > m_worker
bool readVerified(const DB_TABLE_INFO &aTable, const std::vector< std::string > &aColumns, std::vector< std::string > &aLabels, const DB_ROW_SINK &aSink, const DB_RESTART_SINK &aRestart)
The verified read itself, inside a load cycle.
static PROVIDER_STATE ProviderState()
Tells whether the ACE OLE DB provider is installed and loads.
bool ListTables(std::vector< std::string > &aTables) override
Lists the tables and views, sorted by name.
DB_STOP_CHECK m_cycleStop
Set by the outermost load cycle, which a verified read checks before every open and pass.
bool ReadAllRows(const DB_TABLE_INFO &aTable, const std::vector< std::string > &aColumns, std::vector< std::string > &aLabels, const DB_ROW_SINK &aSink, const DB_RESTART_SINK &aRestart) override
A verified read.
std::optional< PASS_DIGEST > readPass(uint64_t aSession, const std::string &aSql, const std::string &aUnorderedSql, size_t aMaxRows, std::vector< std::string > &aLabels, const DB_ROW_SINK *aSink, std::optional< ACE_ENGINE_OP::CLOCK::time_point > aDeadline=std::nullopt)
Runs one pass of aSql on aSession, recording a failure as the backend's.
std::vector< CYCLE_SESSION > m_cycleSessions
void BeginLoadCycle(const DB_STOP_CHECK &aStopped) override
Brackets a load cycle, in which the caller reads each table once.
bool pauseUnlessStopped(std::chrono::milliseconds aWait)
Sleeps for aWait; false if the cycle's stop check came true first.
static constexpr size_t MAX_CYCLE_SESSIONS
The most sessions a load cycle holds at once.
void failStopped(const std::string &aTable)
void EndLoadCycle() override
Closes the sessions the cycle opened, on the worker thread.
bool closeOnWorker(std::vector< uint64_t > aIds, std::shared_ptr< ACE_ENGINE_OP > aOp, std::optional< std::chrono::milliseconds > aBound=std::nullopt)
Closes aIds on the worker thread under aOp, recording a close that lost the engine lock; false if the...
void fail(DB_FAILURE aFailure, std::string aError)
std::wstring m_progId
The provider that opened the connection, which the cycle's sessions use too.
std::optional< DB_TABLE_INFO > DescribeTable(const std::string &aTable) override
int m_cycleDepth
How many load cycles are open; the sessions are closed when the last one ends.
std::optional< R > runOnWorker(std::function< R(DB_JOB_CONTEXT &)> aJob, const DB_ROW_SINK *aSink, std::shared_ptr< ACE_ENGINE_OP > aOp=nullptr, std::optional< std::chrono::milliseconds > aProgressBound=std::nullopt)
Runs aJob on the worker, recording why it did not finish as the failure.
bool requireConnection()
Fails with a message and returns false unless a connection is open.
std::optional< uint64_t > freshSession(const std::string &aTable, ACE_ENGINE_OP::CLOCK::time_point aDeadline, const std::function< bool()> &aMayStart)
A session of the cycle that has not read aTable, now marked as having read it, or nullopt if one coul...
std::atomic< bool > m_connected
bool closeSpentSessions(ACE_ENGINE_OP::CLOCK::time_point aDeadline)
Closes the oldest sessions of the cycle, which have all read the current table, so that one more fits...
void Disconnect() override
uint64_t m_session
Names this backend's connection among those the worker thread holds.
@ BUSY
The check could not start because the engine lock stayed out of reach.
@ PENDING
The check has not finished, so nothing is known yet.
static std::string ProviderMissingMessage()
The message for a source that needs the provider when ProviderState() is MISSING.
bool cycleStopped() const
static std::string ProviderPendingMessage()
The message for a source whose provider check has not finished.
std::shared_ptr< ACE_ENGINE_OP > m_lastOp
The operation the caller last ran, which a failure that drops the connection continues.
void failCall(DB_FAILURE aFailure, std::string aMessage)
Records a failure of a call that ran, and drops the connection if the failure means it cannot be trus...
static constexpr size_t ALL_ROWS
Passed as ReadTable()'s row limit to read every row.
What a job running on a DB_WORKER_THREAD uses to hand rows to the caller.
static constexpr size_t BATCH_ROWS
bool Flush()
Delivers the rows still held back and waits until the caller's sink has taken every one.
bool Emit(std::vector< DB_VALUE > &&aRow)
Queues a row for the caller's sink.
Runs a slow probe once on a thread of its own and keeps its answer for good.
std::optional< T > Get(std::chrono::milliseconds aWait)
The probe's answer, or nothing if it has not finished within aWait. A probe that throws answers T().
Hands rows from a worker job to a caller's sink and gives the caller the column labels.
bool SinkThrew() const
True if the caller's sink threw.
const std::shared_ptr< std::vector< std::string > > & Labels() const
The labels the job fills in.
void Finish()
Copies the labels out if no row did, after the call returned a result.
const DB_ROW_SINK * Sink() const
The sink to pass to the worker call.
static std::string SinkFailureMessage()
The message for a read the caller's sink gave up on.
Runs one job at a time on its own thread so that a call stuck in filesystem I/O, which nothing can in...
static std::chrono::milliseconds CallerBound(int aTimeoutSeconds)
Object to store and handle common variant information.
std::function< bool()> DB_STOP_CHECK
True once the caller no longer wants what a read would deliver.
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.
@ LOGIN
The server rejected the user name or password.
@ PROVIDER_MISSING
The database engine needed to open the file is not installed.
@ UNREACHABLE
Nothing answered at the server address, or it refused the connection.
@ FILE_IN_USE
The database file is locked by another process.
@ FILE_NOT_FOUND
The database file does not exist.
std::function< void()> DB_RESTART_SINK
Told that the rows delivered so far are void, because the read starts the table again from its first ...
std::string NormalizeDecimalText(const std::string &aText)
Trim redundant trailing zeros and a dangling decimal point from decimal text, for example "4700000....
std::string DatabaseValueToString(const DB_VALUE &aValue)
Convert a database value to the text KiCad stores for it.
std::variant< std::monostate, std::string, int64_t, double, bool > DB_VALUE
A single database cell, where monostate is SQL NULL.
DB_WORKER_FAILURE DescribeWorkerOutcome(DB_WORKER_THREAD::OUTCOME aOutcome, const std::string &aPath)
Maps the outcome of a call that returned no result to a failure and a connection decision.
std::string ScrubSecret(std::string aText, const std::string &aSecret)
Replaces a secret in a message, as ***.
std::string RedactDetail(const std::string &aDetail, const std::string &aSecret)
The provider text to show for a call made with aSecret, which is nothing at all when the secret is sh...
std::string utf8(const wxString &aText)
std::chrono::milliseconds FileInUseBackoff(int aAttempt, std::minstd_rand &aRng)
const wxChar *const traceDatabase
Flag to enable database library tracing.
std::chrono::steady_clock CLOCK
_OUT_STRING AsString(const std::string &aString)
A session opened for one load cycle, and the tables it has read, which it never reads again.
What one complete pass over a table returned, as a verified read compares it.
One caller operation's claim on the Access engine lock.
std::chrono::steady_clock CLOCK
std::atomic< STATE > state
@ WAITING
A job is blocked on the engine lock.
@ ACQUIRED
The job took the lock.
@ EXPIRED
The deadline passed first; never cleared.
const CLOCK::time_point deadline
A column as the backend reports it.
bool supported
False for columns the backend can never read, such as attachments.
std::string name
The table's name as the backend spells it.
What a backend reports, and what it does with its connection, when a worker call did not complete.
@ DROP
The connection is closed through the worker.
@ FORGET
The stuck job releases the connection itself when its blocked call returns.
static std::vector< uint8_t > bytes(const std::string &aText)
std::vector< DATABASE_CONNECTION::ROW > rows
KIBIS top(path, &reporter)
wxString result
Test unit parsing edge cases and error handling.