KiCad PCB EDA Suite
Loading...
Searching...
No Matches
ace_backend.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "ace_backend.h"
21
22#include "db_backend_support.h"
23
24#include <algorithm>
25#include <atomic>
26#include <charconv>
27#include <chrono>
28#include <cstdint>
29#include <cstdlib>
30#include <cwctype>
31#include <future>
32#include <map>
33#include <memory>
34#include <mutex>
35#include <optional>
36#include <random>
37#include <system_error>
38#include <thread>
39#include <variant>
40
41#include <fmt/format.h>
42
43#include <wx/intl.h>
44#include <wx/log.h>
45#include <wx/string.h>
46
50
51#include "access_engine_mutex.h"
52
53#include <windows.h>
54#include <objbase.h>
55#include <oleauto.h>
56#include <winnetwk.h>
57
58using namespace std::chrono_literals;
59
60namespace
61{
62
63// Values of the ADO enumerations this file uses
64constexpr long adModeRead = 1;
65constexpr long adModeShareDenyNone = 16;
66constexpr long adCmdText = 1;
67constexpr long adSchemaColumns = 4;
68constexpr long adSchemaTables = 20;
69constexpr long GET_ROWS_BATCH = static_cast<long>( DB_JOB_CONTEXT::BATCH_ROWS );
70
71constexpr wchar_t PROG_ID_CURRENT[] = L"Microsoft.ACE.OLEDB.16.0";
72constexpr wchar_t PROG_ID_FALLBACK[] = L"Microsoft.ACE.OLEDB.12.0";
73
74
75std::wstring toWide( const std::string& aUtf8 )
76{
77 return std::wstring( wxString::FromUTF8( aUtf8 ).wc_str() );
78}
79
80
81std::string toUtf8( const wchar_t* aWide, size_t aLength )
82{
83 return std::string( wxString( aWide, aLength ).ToUTF8() );
84}
85
86
87std::string quoted( const std::string& aIdentifier )
88{
89 // Access names cannot hold a bracket, so no escaping is needed
90 return "[" + aIdentifier + "]";
91}
92
93
95std::string selectList( const std::vector<std::string>& aColumns )
96{
97 if( aColumns.empty() )
98 return "*";
99
100 std::string list;
101
102 for( size_t i = 0; i < aColumns.size(); ++i )
103 list += ( i ? ", " : "" ) + quoted( aColumns[i] );
104
105 return list;
106}
107
108
110struct ADO_ERROR
111{
112 HRESULT hr = E_FAIL;
113 std::string text;
114 std::string sqlState = "";
115};
116
117
118std::string hresultText( HRESULT aHr )
119{
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,
124 nullptr );
125 std::string text;
126
127 if( length && buffer )
128 text = toUtf8( buffer, length );
129 else
130 text = fmt::format( "HRESULT 0x{:08X}", static_cast<unsigned long>( aHr ) );
131
132 if( buffer )
133 LocalFree( buffer );
134
135 while( !text.empty() && ( text.back() == '\r' || text.back() == '\n' || text.back() == ' ' ) )
136 text.pop_back();
137
138 return text;
139}
140
141
143class VAR
144{
145public:
146 VAR() { VariantInit( &m_v ); }
147
148 ~VAR() { VariantClear( &m_v ); }
149
150 VAR( const VAR& ) = delete;
151 VAR& operator=( const VAR& ) = delete;
152
153 VAR( VAR&& aOther ) noexcept
154 {
155 m_v = aOther.m_v;
156 VariantInit( &aOther.m_v );
157 }
158
159 VAR& operator=( VAR&& aOther ) noexcept
160 {
161 if( this != &aOther )
162 {
163 VariantClear( &m_v );
164 m_v = aOther.m_v;
165 VariantInit( &aOther.m_v );
166 }
167
168 return *this;
169 }
170
171 static VAR Str( const std::wstring& aText )
172 {
173 VAR v;
174
175 V_VT( &v.m_v ) = VT_BSTR;
176 V_BSTR( &v.m_v ) = SysAllocStringLen( aText.data(), static_cast<UINT>( aText.size() ) );
177 return v;
178 }
179
180 static VAR Long( long aValue )
181 {
182 VAR v;
183
184 V_VT( &v.m_v ) = VT_I4;
185 V_I4( &v.m_v ) = aValue;
186 return v;
187 }
188
190 static VAR Missing()
191 {
192 VAR v;
193
194 V_VT( &v.m_v ) = VT_ERROR;
195 V_ERROR( &v.m_v ) = DISP_E_PARAMNOTFOUND;
196 return v;
197 }
198
200 static VAR TableCriteria( const std::wstring& aTable )
201 {
202 SAFEARRAYBOUND bound = { 3, 0 };
203 SAFEARRAY* array = SafeArrayCreate( VT_VARIANT, 1, &bound );
204
205 if( !array )
206 throw ADO_ERROR{ E_OUTOFMEMORY, std::string() };
207
208 VAR name = Str( aTable );
209 LONG index = 2;
210 VAR v;
211
212 V_VT( &v.m_v ) = VT_ARRAY | VT_VARIANT;
213 V_ARRAY( &v.m_v ) = array;
214
215 const HRESULT hr = SafeArrayPutElement( array, &index, &name.m_v );
216
217 if( FAILED( hr ) )
218 throw ADO_ERROR{ hr, std::string() };
219
220 return v;
221 }
222
223 const VARIANT& Raw() const { return m_v; }
224
225 VARIANT* Out() { return &m_v; }
226
227 bool AsBool() const
228 {
229 VAR copy;
230 #if defined (__MINGW32__)
231 if( FAILED( VariantChangeTypeEx( &copy.m_v, const_cast<VARIANT*>(&m_v), LOCALE_INVARIANT, 0, VT_BOOL ) ) )
232 #else
233 if( FAILED( VariantChangeTypeEx( &copy.m_v, &m_v, LOCALE_INVARIANT, 0, VT_BOOL ) ) )
234 #endif
235 return false;
236
237 return V_BOOL( &copy.m_v ) != VARIANT_FALSE;
238 }
239
240 long AsLong() const
241 {
242 VAR copy;
243
244 #if defined (__MINGW32__)
245 if( FAILED( VariantChangeTypeEx( &copy.m_v, const_cast<VARIANT*>(&m_v), LOCALE_INVARIANT, 0, VT_I4 ) ) )
246 #else
247 if( FAILED( VariantChangeTypeEx( &copy.m_v, &m_v, LOCALE_INVARIANT, 0, VT_I4 ) ) )
248 #endif
249 return 0;
250
251 return V_I4( &copy.m_v );
252 }
253
254 std::string AsString() const
255 {
256 VAR copy;
257
258 #if defined (__MINGW32__)
259 if( FAILED( VariantChangeTypeEx( &copy.m_v, const_cast<VARIANT*>(&m_v), LOCALE_INVARIANT, 0, VT_BSTR ) ) )
260 #else
261 if( FAILED( VariantChangeTypeEx( &copy.m_v, &m_v, LOCALE_INVARIANT, 0, VT_BSTR ) ) )
262 #endif
263 return std::string();
264
265 const BSTR text = V_BSTR( &copy.m_v );
266
267 return text ? toUtf8( text, SysStringLen( text ) ) : std::string();
268 }
269
270 IDispatch* Dispatch() const { return V_VT( &m_v ) == VT_DISPATCH ? V_DISPATCH( &m_v ) : nullptr; }
271
272private:
273 VARIANT m_v;
274};
275
276
278class DISP
279{
280public:
281 DISP() = default;
282
283 explicit DISP( IDispatch* aOwned ) : m_p( aOwned ) {}
284
285 ~DISP() { Reset(); }
286
287 DISP( const DISP& ) = delete;
288 DISP& operator=( const DISP& ) = delete;
289
290 DISP( DISP&& aOther ) noexcept : m_p( aOther.m_p ), m_ids( std::move( aOther.m_ids ) ) { aOther.m_p = nullptr; }
291
292 DISP& operator=( DISP&& aOther ) noexcept
293 {
294 if( this != &aOther )
295 {
296 Reset();
297 m_p = aOther.m_p;
298 m_ids = std::move( aOther.m_ids );
299 aOther.m_p = nullptr;
300 }
301
302 return *this;
303 }
304
305 static DISP Create( const wchar_t* aProgId )
306 {
307 CLSID clsid;
308 HRESULT hr = CLSIDFromProgID( aProgId, &clsid );
309
310 if( FAILED( hr ) )
311 throw ADO_ERROR{ hr, std::string() };
312
313 IDispatch* object = nullptr;
314
315 hr = CoCreateInstance( clsid, nullptr, CLSCTX_INPROC_SERVER, IID_IDispatch,
316 reinterpret_cast<void**>( &object ) );
317
318 if( FAILED( hr ) )
319 throw ADO_ERROR{ hr, std::string() };
320
321 return DISP( object );
322 }
323
325 static DISP From( const VAR& aValue )
326 {
327 IDispatch* object = aValue.Dispatch();
328
329 if( !object )
330 throw ADO_ERROR{ E_POINTER, std::string() };
331
332 object->AddRef();
333 return DISP( object );
334 }
335
336 void Reset()
337 {
338 if( m_p )
339 {
340 m_p->Release();
341 m_p = nullptr;
342 }
343
344 m_ids.clear();
345 }
346
348 template <typename... ARGS>
349 VAR Property( const wchar_t* aName, const ARGS&... aArgs )
350 {
351 return invoke( aName, DISPATCH_PROPERTYGET, { aArgs.Raw()... } );
352 }
353
354 void SetProperty( const wchar_t* aName, const VAR& aValue )
355 {
356 invoke( aName, DISPATCH_PROPERTYPUT, { aValue.Raw() } );
357 }
358
359 template <typename... ARGS>
360 VAR Call( const wchar_t* aName, const ARGS&... aArgs )
361 {
362 return invoke( aName, DISPATCH_METHOD, { aArgs.Raw()... } );
363 }
364
365private:
366 VAR invoke( const wchar_t* aName, WORD aFlags, std::vector<VARIANT> aArgs )
367 {
368 auto found = m_ids.find( aName );
369
370 if( found == m_ids.end() )
371 {
372 DISPID id = 0;
373 LPOLESTR name = const_cast<LPOLESTR>( aName );
374 HRESULT hr = m_p->GetIDsOfNames( IID_NULL, &name, 1, LOCALE_USER_DEFAULT, &id );
375
376 if( FAILED( hr ) )
377 throw ADO_ERROR{ hr, std::string() };
378
379 found = m_ids.emplace( aName, id ).first;
380 }
381
382 // IDispatch takes the arguments last to first
383 std::vector<VARIANT> raw( aArgs.rbegin(), aArgs.rend() );
384
385 DISPPARAMS params = {};
386 DISPID putId = DISPID_PROPERTYPUT;
387
388 params.cArgs = static_cast<UINT>( raw.size() );
389 params.rgvarg = raw.empty() ? nullptr : raw.data();
390
391 if( aFlags == DISPATCH_PROPERTYPUT )
392 {
393 params.cNamedArgs = 1;
394 params.rgdispidNamedArgs = &putId;
395 }
396
397 VAR result;
398 EXCEPINFO info = {};
399 UINT argError = 0;
400 HRESULT hr = m_p->Invoke( found->second, IID_NULL, LOCALE_USER_DEFAULT, aFlags, &params, result.Out(),
401 &info, &argError );
402
403 if( FAILED( hr ) )
404 {
405 if( hr == DISP_E_EXCEPTION && info.pfnDeferredFillIn )
406 info.pfnDeferredFillIn( &info );
407
408 ADO_ERROR error{ hr == DISP_E_EXCEPTION && FAILED( info.scode ) ? info.scode : hr, std::string() };
409
410 if( info.bstrDescription )
411 error.text = toUtf8( info.bstrDescription, SysStringLen( info.bstrDescription ) );
412
413 SysFreeString( info.bstrSource );
414 SysFreeString( info.bstrDescription );
415 SysFreeString( info.bstrHelpFile );
416 throw error;
417 }
418
419 return result;
420 }
421
422 IDispatch* m_p = nullptr;
423 std::map<std::wstring, DISPID> m_ids;
424};
425
426
428void clearProviderErrors( DISP& aConnection )
429{
430 try
431 {
432 DISP errors = DISP::From( aConnection.Property( L"Errors" ) );
433
434 errors.Call( L"Clear" );
435 }
436 catch( const ADO_ERROR& )
437 {
438 }
439}
440
441
444void applyProviderErrors( DISP& aConnection, ADO_ERROR& aError )
445{
446 std::string text;
447 std::string sqlState;
448
449 try
450 {
451 DISP errors = DISP::From( aConnection.Property( L"Errors" ) );
452 long count = errors.Property( L"Count" ).AsLong();
453
454 for( long i = 0; i < count; ++i )
455 {
456 DISP error = DISP::From( errors.Property( L"Item", VAR::Long( i ) ) );
457 std::string description = error.Property( L"Description" ).AsString();
458
459 if( sqlState.empty() )
460 sqlState = error.Property( L"SQLState" ).AsString();
461
462 if( description.empty() || text.find( description ) != std::string::npos )
463 continue;
464
465 text += ( text.empty() ? "" : "; " ) + description;
466 }
467 }
468 catch( const ADO_ERROR& )
469 {
470 }
471
472 clearProviderErrors( aConnection );
473
474 if( !text.empty() )
475 aError.text = text;
476
477 aError.sqlState = sqlState;
478}
479
480
482struct CALL_RESULT
483{
484 bool ok = true;
486 std::string detail;
487 bool engineBusy = false;
488};
489
490
491CALL_RESULT failed( DB_FAILURE aFailure, std::string aDetail )
492{
493 return { false, aFailure, std::move( aDetail ) };
494}
495
496
498struct CATALOG_RESULT
499{
500 CALL_RESULT call;
501 std::vector<std::string> names;
502 std::string table;
503 bool found = false;
504 std::vector<DB_COLUMN> columns;
505};
506
507
508bool isNetworkError( DWORD aCode )
509{
510 switch( aCode )
511 {
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:
525 return true;
526
527 default:
528 return false;
529 }
530}
531
532
534bool isProviderMissing( HRESULT aHr )
535{
536 constexpr HRESULT ADO_PROVIDER_NOT_FOUND = static_cast<HRESULT>( 0x800A0E7A );
537
538 return aHr == ADO_PROVIDER_NOT_FOUND || aHr == REGDB_E_CLASSNOTREG || aHr == CO_E_CLASSSTRING
539 || aHr == HRESULT_FROM_WIN32( ERROR_MOD_NOT_FOUND );
540}
541
542
544std::optional<DB_FAILURE> classifyByNumber( const std::string& aSqlState )
545{
546 int number = 0;
547 auto [end, code] = std::from_chars( aSqlState.data(), aSqlState.data() + aSqlState.size(), number );
548
549 if( code != std::errc() || end != aSqlState.data() + aSqlState.size() )
550 return std::nullopt;
551
552 switch( number )
553 {
554 case 3031: return DB_FAILURE::LOGIN; // not a valid password
555 case 3024: return DB_FAILURE::FILE_NOT_FOUND; // could not find file
556 case 3043: // disk or network error
557 case 3044: return DB_FAILURE::UNREACHABLE; // not a valid path
558 case 3006: // exclusively locked
559 case 3045: // file already in use
560 case 3050: // could not lock file
561 case 3051: // opened exclusively by another user, or no permission
562 case 3356: // opened by another user in a way that conflicts
563 case 3704: // already opened exclusively
564 case 3734: return DB_FAILURE::FILE_IN_USE; // placed in a state that prevents opening or locking
565 default: return DB_FAILURE::UNKNOWN;
566 }
567}
568
569
571struct FIXED_MESSAGE
572{
573 const wchar_t* text;
574 DB_FAILURE failure;
575};
576
577
578const FIXED_MESSAGE FIXED_MESSAGES[] = {
579 { L"not a valid password.", DB_FAILURE::LOGIN },
580 { L"could not use ''; file already in use.", DB_FAILURE::FILE_IN_USE },
581 { L"could not lock file.", DB_FAILURE::FILE_IN_USE },
582 { L"disk or network error.", DB_FAILURE::UNREACHABLE },
583};
584
585
587DB_FAILURE classifyFailure( const ADO_ERROR& aError, const std::string& aText )
588{
589 if( std::optional<DB_FAILURE> byNumber = classifyByNumber( aError.sqlState ) )
590 return *byNumber;
591
592 if( isProviderMissing( aError.hr ) )
594
595 if( HRESULT_FACILITY( aError.hr ) == FACILITY_WIN32 && isNetworkError( HRESULT_CODE( aError.hr ) ) )
597
598 std::wstring lower = toWide( aText );
599
600 std::transform( lower.begin(), lower.end(), lower.begin(),
601 []( wchar_t c )
602 {
603 return static_cast<wchar_t>( std::towlower( c ) );
604 } );
605
606 for( const FIXED_MESSAGE& message : FIXED_MESSAGES )
607 {
608 if( lower == message.text )
609 return message.failure;
610 }
611
612 return DB_FAILURE::UNKNOWN;
613}
614
615
617struct ADO_SESSION
618{
619 DISP connection;
620};
621
622
623// COM objects belong to the thread that made them, so the sessions of a backend stay in its worker's thread
624// Built on first use, since a namespace-scope thread_local would be built on every thread of the process
625std::map<uint64_t, ADO_SESSION>& sessions()
626{
627 thread_local std::map<uint64_t, ADO_SESSION> s_sessions;
628 return s_sessions;
629}
630
631std::atomic<uint64_t> g_nextSession{ 1 };
632
633
634ADO_SESSION& sessionFor( uint64_t aSession )
635{
636 auto it = sessions().find( aSession );
637
638 if( it == sessions().end() )
639 throw ADO_ERROR{ HRESULT_FROM_WIN32( ERROR_CONNECTION_UNAVAIL ), "the connection to the database was lost" };
640
641 return it->second;
642}
643
644
647template <typename BODY>
648CALL_RESULT guarded( BODY aBody, uint64_t aSession = 0 )
649{
650 try
651 {
652 auto opened = sessions().find( aSession );
653
654 if( opened != sessions().end() )
655 clearProviderErrors( opened->second.connection );
656
657 return aBody();
658 }
659 catch( ADO_ERROR& error )
660 {
661 auto session = sessions().find( aSession );
662
663 if( error.sqlState.empty() && session != sessions().end() )
664 applyProviderErrors( session->second.connection, error );
665
666 const std::string text = error.text.empty() ? hresultText( error.hr ) : error.text;
667 const DB_FAILURE failure = classifyFailure( error, text );
668
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 ) );
672
673 return failed( failure, text );
674 }
675 catch( const std::exception& error )
676 {
677 return failed( DB_FAILURE::UNKNOWN, error.what() );
678 }
679}
680
681
682bool comThreadInit()
683{
684 return SUCCEEDED( CoInitializeEx( nullptr, COINIT_APARTMENTTHREADED ) );
685}
686
687
689thread_local std::shared_ptr<ACE_ENGINE_OP> s_threadOp;
690
691
693std::unique_lock<std::timed_mutex> lockEngine( ACE_ENGINE_OP& aOp )
694{
695 // An expired operation stays expired, so a later close of it neither waits again nor loses the timeout
697 return {};
698
700
701 std::unique_lock<std::timed_mutex> lock = LockAccessEngine( aOp.deadline );
702
704 return lock;
705}
706
707
709std::chrono::milliseconds remainingUntil( ACE_ENGINE_OP::CLOCK::time_point aDeadline )
710{
711 return std::max( std::chrono::milliseconds( 0 ),
712 std::chrono::ceil<std::chrono::milliseconds>( aDeadline - ACE_ENGINE_OP::CLOCK::now() ) );
713}
714
715
716// A session left open by a close that lost the lock is never released, so its thread never unloads the provider
717thread_local bool s_leftSessionOpen = false;
718
719
720void comThreadExit()
721{
722 // A thread that never ran a job has no operation to wait out, so it tries the lock once
723 const ACE_ENGINE_OP::CLOCK::time_point deadline =
724 s_threadOp ? s_threadOp->deadline : ACE_ENGINE_OP::CLOCK::time_point();
725
726 // Dropping the last reference to the engine unloads Office libraries, which must not overlap a load elsewhere
727 std::unique_lock lock = LockAccessEngine( deadline );
728
729 if( !lock.owns_lock() || s_leftSessionOpen )
730 {
731 // Freeing the sessions unlocked would race the unload, so they stay for good and the thread simply ends
733 new std::map<uint64_t, ADO_SESSION>( std::move( sessions() ) );
734 return;
735 }
736
737 sessions().clear();
738 CoUninitialize();
739}
740
741
742void closeSession( uint64_t aSession, ACE_ENGINE_OP& aOp )
743{
744 auto it = sessions().find( aSession );
745
746 if( it == sessions().end() )
747 return;
748
749 std::unique_lock lock = lockEngine( aOp );
750
751 if( !lock.owns_lock() )
752 {
753 // Closing unlocked would race the engine unload, so the session stays open for good
755 new ADO_SESSION( std::move( it->second ) );
756 sessions().erase( it );
757 s_leftSessionOpen = true;
758 return;
759 }
760
761 try
762 {
763 it->second.connection.Call( L"Close" );
764 }
765 catch( const ADO_ERROR& )
766 {
767 }
768
769 sessions().erase( it );
770}
771
772
774struct PROBE_ANSWER
775{
776 std::wstring progId;
777 std::string error;
778 bool busy = false;
779};
780
781
784constexpr std::chrono::seconds PROBE_WAIT{ 10 };
785
786
788PROBE_ANSWER findProvider()
789{
790 PROBE_ANSWER found;
791
792 try
793 {
794 // The lock is declared first so that it outlives CoUninitialize, which can unload the engine
795 std::unique_lock lock = LockAccessEngine( std::chrono::steady_clock::now() + PROBE_WAIT );
796
797 if( !lock.owns_lock() )
798 {
799 found.busy = true;
800 return found;
801 }
802
803 if( !comThreadInit() )
804 {
805 found.error = "COM could not be initialized";
806 return found;
807 }
808
809 struct COM_SCOPE
810 {
811 ~COM_SCOPE() { CoUninitialize(); }
812 } comScope;
813
814 for( const wchar_t* candidate : { PROG_ID_CURRENT, PROG_ID_FALLBACK } )
815 {
816 CLSID clsid;
817
818 if( FAILED( CLSIDFromProgID( candidate, &clsid ) ) )
819 continue;
820
821 IUnknown* provider = nullptr;
822
823 if( SUCCEEDED( CoCreateInstance( clsid, nullptr, CLSCTX_INPROC_SERVER, IID_IUnknown,
824 reinterpret_cast<void**>( &provider ) ) ) )
825 {
826 provider->Release();
827 found.progId = candidate;
828 break;
829 }
830 }
831 }
832 catch( const std::exception& error )
833 {
834 found.progId.clear();
835 found.error = error.what();
836 wxLogTrace( traceDatabase, wxT( "ACE_BACKEND: provider probe failed: %s" ), wxString::FromUTF8( found.error ) );
837 }
838
839 return found;
840}
841
842
844std::optional<PROBE_ANSWER> probeAnswer( std::chrono::milliseconds aWait )
845{
846 static auto* s_mutex = new std::mutex;
847
848 // Leaked so a probe thread still waiting on the engine never outlives its latch
849 static DB_PROBE_LATCH<PROBE_ANSWER>* s_probe = new DB_PROBE_LATCH<PROBE_ANSWER>( findProvider );
850
852
853 {
854 std::lock_guard lock( *s_mutex );
855 probe = s_probe;
856 }
857
858 std::optional<PROBE_ANSWER> answer = probe->Get( aWait );
859
860 // A probe that never got the engine says nothing about the provider, so the next call probes afresh
861 if( answer && answer->busy )
862 {
863 std::lock_guard lock( *s_mutex );
864
865 if( s_probe == probe )
866 s_probe = new DB_PROBE_LATCH<PROBE_ANSWER>( findProvider );
867 }
868
869 return answer;
870}
871
872
874ACE_BACKEND::PROVIDER_STATE providerLookup( std::wstring& aProgId, std::chrono::milliseconds aWait )
875{
876 std::optional<PROBE_ANSWER> found = probeAnswer( aWait );
877
878 if( !found )
880
881 if( found->busy )
883
884 aProgId = found->progId;
886}
887
888
890std::wstring universalPath( const std::wstring& aPath )
891{
892 if( aPath.size() < 2 || aPath[1] != L':' )
893 return aPath;
894
895 DWORD size = 1024;
896 std::vector<BYTE> buffer( size );
897 DWORD code = WNetGetUniversalNameW( aPath.c_str(), UNIVERSAL_NAME_INFO_LEVEL, buffer.data(), &size );
898
899 if( code == ERROR_MORE_DATA )
900 {
901 buffer.resize( size );
902 code = WNetGetUniversalNameW( aPath.c_str(), UNIVERSAL_NAME_INFO_LEVEL, buffer.data(), &size );
903 }
904
905 if( code != NO_ERROR )
906 return aPath;
907
908 return reinterpret_cast<UNIVERSAL_NAME_INFOW*>( buffer.data() )->lpUniversalName;
909}
910
911
913std::wstring quotedValue( const std::wstring& aValue )
914{
915 std::wstring result = L"\"";
916
917 for( wchar_t c : aValue )
918 {
919 if( c == L'"' )
920 result += L'"';
921
922 result += c;
923 }
924
925 return result + L"\"";
926}
927
928
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 )
931{
932 return guarded(
933 [&]() -> CALL_RESULT
934 {
935 const std::wstring path = universalPath( toWide( aPath ) );
936
937 if( GetFileAttributesW( path.c_str() ) == INVALID_FILE_ATTRIBUTES )
938 {
939 const DWORD code = GetLastError();
940
941 if( code == ERROR_FILE_NOT_FOUND || code == ERROR_PATH_NOT_FOUND )
942 return failed( DB_FAILURE::FILE_NOT_FOUND, std::string() );
943
944 if( isNetworkError( code ) )
945 return failed( DB_FAILURE::UNREACHABLE, hresultText( HRESULT_FROM_WIN32( code ) ) );
946 }
947
948 std::wstring connectionString = L"Provider=" + aProgId + L";Data Source=" + quotedValue( path ) + L";";
949
950 if( !aPassword.empty() )
951 {
952 connectionString += L"Jet OLEDB:Database Password=" + quotedValue( toWide( aPassword ) ) + L";";
953 }
954
955 // The engine loads and unloads Office libraries that race when the ODBC driver does the same
956 std::unique_lock lock = lockEngine( aOp );
957
958 if( !lock.owns_lock() )
959 {
960 CALL_RESULT busy = failed( DB_FAILURE::TIMEOUT, std::string() );
961 busy.engineBusy = true;
962 return busy;
963 }
964
965 DISP connection = DISP::Create( L"ADODB.Connection" );
966
967 connection.SetProperty( L"Mode", VAR::Long( adModeRead | adModeShareDenyNone ) );
968
969 if( aTimeout > 0 )
970 {
971 connection.SetProperty( L"ConnectionTimeout", VAR::Long( aTimeout ) );
972 connection.SetProperty( L"CommandTimeout", VAR::Long( aTimeout ) );
973 }
974
975 try
976 {
977 connection.Call( L"Open", VAR::Str( connectionString ) );
978 }
979 catch( ADO_ERROR& error )
980 {
981 applyProviderErrors( connection, error );
982 throw;
983 }
984
985 sessions()[aSession].connection = std::move( connection );
986 return {};
987 },
988 aSession );
989}
990
991
992DB_VALUE convertValue( const VARIANT& aValue );
993
994
996void checkBatch( HRESULT aHr )
997{
998 if( FAILED( aHr ) )
999 throw ADO_ERROR{ aHr, "invalid row batch" };
1000}
1001
1002
1004template <typename ON_ROW>
1005void forEachRow( DISP& aRecordset, ON_ROW aOnRow )
1006{
1007 for( ;; )
1008 {
1009 if( aRecordset.Property( L"EOF" ).AsBool() )
1010 return;
1011
1012 VAR batch = aRecordset.Call( L"GetRows", VAR::Long( GET_ROWS_BATCH ) );
1013
1014 // A batch that cannot be read would end the pass early, and two passes cut alike would agree
1015 if( V_VT( &batch.Raw() ) != ( VT_ARRAY | VT_VARIANT ) || !V_ARRAY( &batch.Raw() ) )
1016 checkBatch( E_FAIL );
1017
1018 SAFEARRAY* array = V_ARRAY( &batch.Raw() );
1019 LONG fieldLower = 0;
1020 LONG fieldUpper = -1;
1021 LONG rowLower = 0;
1022 LONG rowUpper = -1;
1023
1024 if( SafeArrayGetDim( array ) != 2 )
1025 checkBatch( E_FAIL );
1026
1027 checkBatch( SafeArrayGetLBound( array, 1, &fieldLower ) );
1028 checkBatch( SafeArrayGetUBound( array, 1, &fieldUpper ) );
1029 checkBatch( SafeArrayGetLBound( array, 2, &rowLower ) );
1030 checkBatch( SafeArrayGetUBound( array, 2, &rowUpper ) );
1031
1032 const size_t fields = static_cast<size_t>( fieldUpper - fieldLower + 1 );
1033 const size_t rows = static_cast<size_t>( rowUpper - rowLower + 1 );
1034
1035 VARIANT* cells = nullptr;
1036
1037 checkBatch( SafeArrayAccessData( array, reinterpret_cast<void**>( &cells ) ) );
1038
1039 bool more = true;
1040
1041 try
1042 {
1043 // The first subscript is the field, and it varies fastest in memory
1044 for( size_t row = 0; row < rows && more; ++row )
1045 {
1046 std::vector<DB_VALUE> values;
1047 values.reserve( fields );
1048
1049 for( size_t field = 0; field < fields; ++field )
1050 values.push_back( convertValue( cells[row * fields + field] ) );
1051
1052 more = aOnRow( std::move( values ) );
1053 }
1054 }
1055 catch( ... )
1056 {
1057 SafeArrayUnaccessData( array );
1058 throw;
1059 }
1060
1061 SafeArrayUnaccessData( array );
1062
1063 if( !more || rows == 0 )
1064 return;
1065 }
1066}
1067
1068
1069std::string bstrText( BSTR aText )
1070{
1071 return aText ? toUtf8( aText, SysStringLen( aText ) ) : std::string();
1072}
1073
1074
1075DB_VALUE convertValue( const VARIANT& aValue )
1076{
1077 VARIANT value = aValue;
1078
1079 // A by-reference variant points at the real value
1080 if( V_VT( &value ) & VT_BYREF )
1081 {
1082 VAR copy;
1083
1084 if( FAILED( VariantCopyInd( copy.Out(), &value ) ) )
1085 return std::monostate();
1086
1087 return convertValue( copy.Raw() );
1088 }
1089
1090 switch( V_VT( &value ) )
1091 {
1092 case VT_EMPTY:
1093 case VT_NULL:
1094 case VT_ERROR:
1095 return std::monostate();
1096
1097 case VT_BSTR:
1098 return bstrText( V_BSTR( &value ) );
1099
1100 case VT_I1:
1101 return static_cast<int64_t>( V_I1( &value ) );
1102
1103 case VT_UI1:
1104 return static_cast<int64_t>( V_UI1( &value ) );
1105
1106 case VT_I2:
1107 return static_cast<int64_t>( V_I2( &value ) );
1108
1109 case VT_UI2:
1110 return static_cast<int64_t>( V_UI2( &value ) );
1111
1112 case VT_I4:
1113 case VT_INT:
1114 return static_cast<int64_t>( V_I4( &value ) );
1115
1116 case VT_UI4:
1117 case VT_UINT:
1118 return static_cast<int64_t>( V_UI4( &value ) );
1119
1120 case VT_I8:
1121 return static_cast<int64_t>( V_I8( &value ) );
1122
1123 case VT_UI8:
1124 if( V_UI8( &value ) > static_cast<ULONGLONG>( INT64_MAX ) )
1125 return std::to_string( V_UI8( &value ) );
1126
1127 return static_cast<int64_t>( V_UI8( &value ) );
1128
1129 case VT_R4:
1130 // The shortest text that reads back as the single, so 0.1 does not become 0.10000000149
1131 // A negative zero would print as -0, which no other numeric column does
1132 return V_R4( &value ) == 0.0f ? std::string( "0" ) : fmt::format( "{}", V_R4( &value ) );
1133
1134 case VT_R8:
1135 return V_R8( &value );
1136
1137 case VT_BOOL:
1138 return V_BOOL( &value ) != VARIANT_FALSE;
1139
1140 case VT_DATE:
1141 {
1142 SYSTEMTIME time;
1143
1144 if( !VariantTimeToSystemTime( V_DATE( &value ), &time ) )
1145 return std::monostate();
1146
1147 return fmt::format( "{:04}-{:02}-{:02} {:02}:{:02}:{:02}", time.wYear, time.wMonth, time.wDay, time.wHour,
1148 time.wMinute, time.wSecond );
1149 }
1150
1151 case VT_CY:
1152 case VT_DECIMAL:
1153 {
1154 BSTR raw = nullptr;
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 );
1157
1158 if( FAILED( hr ) )
1159 return std::monostate();
1160
1161 std::unique_ptr<OLECHAR, decltype( &SysFreeString )> text( raw, &SysFreeString );
1162
1163 return NormalizeDecimalText( bstrText( text.get() ) );
1164 }
1165
1166 case VT_ARRAY | VT_UI1:
1167 return std::monostate();
1168
1169 default:
1170 {
1171 VAR text;
1172
1173 if( FAILED( VariantChangeTypeEx( text.Out(), &value, LOCALE_INVARIANT, 0, VT_BSTR ) ) )
1174 return std::monostate();
1175
1176 return bstrText( V_BSTR( &text.Raw() ) );
1177 }
1178 }
1179}
1180
1181
1183long fieldIndex( DISP& aRecordset, const wchar_t* aName )
1184{
1185 DISP fields = DISP::From( aRecordset.Property( L"Fields" ) );
1186 long count = fields.Property( L"Count" ).AsLong();
1187
1188 for( long i = 0; i < count; ++i )
1189 {
1190 DISP field = DISP::From( fields.Property( L"Item", VAR::Long( i ) ) );
1191
1192 if( _wcsicmp( toWide( field.Property( L"Name" ).AsString() ).c_str(), aName ) == 0 )
1193 return i;
1194 }
1195
1196 throw ADO_ERROR{ E_FAIL, "the provider's catalog lacks the column " + toUtf8( aName, wcslen( aName ) ) };
1197}
1198
1199
1201int64_t asInteger( const DB_VALUE& aValue )
1202{
1203 if( const int64_t* integer = std::get_if<int64_t>( &aValue ) )
1204 return *integer;
1205
1206 if( const double* real = std::get_if<double>( &aValue ) )
1207 return static_cast<int64_t>( *real );
1208
1209 if( const std::string* text = std::get_if<std::string>( &aValue ) )
1210 return std::strtoll( text->c_str(), nullptr, 10 );
1211
1212 return 0;
1213}
1214
1215
1217void describeType( long aOleDbType, int& aOdbcType, bool& aSupported )
1218{
1219 // OLE DB DBTYPE values and the ODBC SQL types they map to
1220 enum OLE_DB_TYPE : long
1221 {
1222 DBTYPE_I2 = 2,
1223 DBTYPE_I4 = 3,
1224 DBTYPE_R4 = 4,
1225 DBTYPE_R8 = 5,
1226 DBTYPE_CY = 6,
1227 DBTYPE_DATE = 7,
1228 DBTYPE_BSTR = 8,
1229 DBTYPE_IDISPATCH = 9,
1230 DBTYPE_BOOL = 11,
1231 DBTYPE_UNKNOWN = 13,
1232 DBTYPE_DECIMAL = 14,
1233 DBTYPE_UI1 = 17,
1234 DBTYPE_I8 = 20,
1235 DBTYPE_GUID = 72,
1236 DBTYPE_BYTES = 128,
1237 DBTYPE_STR = 129,
1238 DBTYPE_WSTR = 130,
1239 DBTYPE_NUMERIC = 131,
1240 DBTYPE_UDT = 132,
1241 DBTYPE_DBDATE = 133,
1242 DBTYPE_DBTIME = 134,
1243 DBTYPE_DBTIMESTAMP = 135,
1244 DBTYPE_CHAPTER = 136,
1245 DBTYPE_VARIANT = 138
1246 };
1247
1248 enum ODBC_SQL_TYPE : int
1249 {
1250 ODBC_SQL_NONE = 0,
1251 ODBC_SQL_NUMERIC = 2,
1252 ODBC_SQL_DECIMAL = 3,
1253 ODBC_SQL_INTEGER = 4,
1254 ODBC_SQL_SMALLINT = 5,
1255 ODBC_SQL_REAL = 7,
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,
1261 ODBC_SQL_BIT = -7,
1262 ODBC_SQL_BINARY = -3,
1263 ODBC_SQL_GUID = -11
1264 };
1265
1266 aSupported = true;
1267 aOdbcType = ODBC_SQL_VARCHAR;
1268
1269 switch( aOleDbType )
1270 {
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;
1282
1283 case DBTYPE_DATE:
1284 case DBTYPE_DBDATE:
1285 case DBTYPE_DBTIME:
1286 case DBTYPE_DBTIMESTAMP: aOdbcType = ODBC_SQL_TYPE_TIMESTAMP; break;
1287
1288 case DBTYPE_BSTR:
1289 case DBTYPE_STR:
1290 case DBTYPE_WSTR: aOdbcType = ODBC_SQL_VARCHAR; break;
1291
1292 // Binary and OLE objects cannot be read as text
1293 case DBTYPE_BYTES:
1294 aOdbcType = ODBC_SQL_BINARY;
1295 aSupported = false;
1296 break;
1297
1298 case DBTYPE_IDISPATCH:
1299 case DBTYPE_UNKNOWN:
1300 case DBTYPE_UDT:
1301 case DBTYPE_CHAPTER:
1302 case DBTYPE_VARIANT:
1303 aOdbcType = ODBC_SQL_NONE;
1304 aSupported = false;
1305 break;
1306
1307 default: break;
1308 }
1309}
1310
1311
1312CATALOG_RESULT listTablesJob( uint64_t aSession )
1313{
1314 CATALOG_RESULT result;
1315
1316 result.call = guarded(
1317 [&]() -> CALL_RESULT
1318 {
1319 ADO_SESSION& session = sessionFor( aSession );
1320 DISP schema =
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" );
1324
1325 forEachRow( schema,
1326 [&]( std::vector<DB_VALUE>&& aRow )
1327 {
1328 const std::string type = DatabaseValueToString( aRow[typeIndex] );
1329 const std::string name = DatabaseValueToString( aRow[nameIndex] );
1330
1331 // Saved select queries are views, and the engine's own bookkeeping tables are not data
1332 if( ( type == "TABLE" || type == "VIEW" ) && name.rfind( "MSys", 0 ) != 0
1333 && name.rfind( "~", 0 ) != 0 )
1334 {
1335 result.names.push_back( name );
1336 }
1337
1338 return true;
1339 } );
1340
1341 schema.Call( L"Close" );
1342 return {};
1343 },
1344 aSession );
1345
1346 return result;
1347}
1348
1349
1350CATALOG_RESULT describeTableJob( uint64_t aSession, const std::string& aTable )
1351{
1352 CATALOG_RESULT result;
1353
1354 result.call = guarded(
1355 [&]() -> CALL_RESULT
1356 {
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" );
1364
1365 std::vector<std::pair<int64_t, DB_COLUMN>> columns;
1366
1367 forEachRow( schema,
1368 [&]( std::vector<DB_VALUE>&& aRow )
1369 {
1370 DB_COLUMN column;
1371
1372 column.name = DatabaseValueToString( aRow[nameIndex] );
1373 describeType( static_cast<long>( asInteger( aRow[typeIndex] ) ), column.type,
1374 column.supported );
1375
1376 if( !result.found )
1377 {
1378 result.table = DatabaseValueToString( aRow[tableIndex] );
1379 result.found = true;
1380 }
1381
1382 columns.emplace_back( asInteger( aRow[positionIndex] ), std::move( column ) );
1383 return true;
1384 } );
1385
1386 schema.Call( L"Close" );
1387
1388 std::stable_sort( columns.begin(), columns.end(),
1389 []( const auto& aLeft, const auto& aRight )
1390 {
1391 return aLeft.first < aRight.first;
1392 } );
1393
1394 for( auto& column : columns )
1395 result.columns.push_back( std::move( column.second ) );
1396
1397 return {};
1398 },
1399 aSession );
1400
1401 return result;
1402}
1403
1404
1406DISP executeQuery( ADO_SESSION& aSession, const std::string& aSql )
1407{
1408 try
1409 {
1410 return DISP::From( aSession.connection.Call( L"Execute", VAR::Str( toWide( aSql ) ), VAR::Missing(),
1411 VAR::Long( adCmdText ) ) );
1412 }
1413 catch( ADO_ERROR& error )
1414 {
1415 applyProviderErrors( aSession.connection, error );
1416 throw;
1417 }
1418}
1419
1420
1422class CONTENT_HASH
1423{
1424public:
1425 void Add( const DB_VALUE& aValue )
1426 {
1427 const uint8_t tag = static_cast<uint8_t>( aValue.index() );
1428 const uint8_t present = std::holds_alternative<std::monostate>( aValue ) ? 0 : 1;
1429
1430 add( &tag, sizeof( tag ) );
1431 add( &present, sizeof( present ) );
1432
1433 std::visit(
1434 [this]( const auto& aCell )
1435 {
1436 payload( aCell );
1437 },
1438 aValue );
1439 }
1440
1441 uint64_t Value() const { return m_hash; }
1442
1443private:
1444 void payload( std::monostate ) { length( 0 ); }
1445
1446 void payload( const std::string& aText )
1447 {
1448 length( aText.size() );
1449 add( aText.data(), aText.size() );
1450 }
1451
1452 void payload( int64_t aValue )
1453 {
1454 length( sizeof( aValue ) );
1455 add( &aValue, sizeof( aValue ) );
1456 }
1457
1458 void payload( double aValue )
1459 {
1460 length( sizeof( aValue ) );
1461 add( &aValue, sizeof( aValue ) );
1462 }
1463
1464 void payload( bool aValue )
1465 {
1466 const uint8_t byte = aValue ? 1 : 0;
1467
1468 length( sizeof( byte ) );
1469 add( &byte, sizeof( byte ) );
1470 }
1471
1472 void length( uint64_t aLength ) { add( &aLength, sizeof( aLength ) ); }
1473
1474 void add( const void* aData, size_t aSize )
1475 {
1476 const uint8_t* bytes = static_cast<const uint8_t*>( aData );
1477
1478 for( size_t i = 0; i < aSize; ++i )
1479 {
1480 m_hash ^= bytes[i];
1481 m_hash *= 0x100000001b3ULL;
1482 }
1483 }
1484
1485 uint64_t m_hash = 0xcbf29ce484222325ULL;
1486};
1487
1488
1490struct PASS_RESULT
1491{
1492 CALL_RESULT call;
1493 size_t rows = 0;
1494 uint64_t hash = 0;
1495};
1496
1497
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,
1501 size_t aMaxRows )
1502{
1503 PASS_RESULT pass;
1504 CONTENT_HASH hash;
1505
1506 pass.call = guarded(
1507 [&]() -> CALL_RESULT
1508 {
1509 ADO_SESSION& session = sessionFor( aSession );
1510 DISP recordset;
1511
1512 try
1513 {
1514 recordset = executeQuery( session, aSql );
1515 }
1516 catch( const ADO_ERROR& error )
1517 {
1518 // Ordering by a memo or OLE column is refused, and an unordered sample is still useful
1519 if( aUnorderedSql.empty() || classifyFailure( error, error.text ) != DB_FAILURE::UNKNOWN )
1520 throw;
1521
1522 recordset = executeQuery( session, aUnorderedSql );
1523 }
1524
1525 {
1526 DISP fields = DISP::From( recordset.Property( L"Fields" ) );
1527 const long count = fields.Property( L"Count" ).AsLong();
1528
1529 for( long i = 0; i < count; ++i )
1530 {
1531 DISP field = DISP::From( fields.Property( L"Item", VAR::Long( i ) ) );
1532
1533 aLabels->push_back( field.Property( L"Name" ).AsString() );
1534 }
1535 }
1536
1537 bool accepted = true;
1538 size_t delivered = 0;
1539
1540 if( aMaxRows != 0 )
1541 {
1542 forEachRow( recordset,
1543 [&]( std::vector<DB_VALUE>&& aRow )
1544 {
1545 // TOP keeps ties, so a bounded read can find more rows than asked for
1546 if( delivered >= aMaxRows )
1547 return false;
1548
1549 ++delivered;
1550
1551 for( const DB_VALUE& value : aRow )
1552 hash.Add( value );
1553
1554 accepted = aContext.Emit( std::move( aRow ) );
1555 return accepted;
1556 } );
1557 }
1558
1559 if( !accepted || !aContext.Flush() )
1560 return failed( DB_FAILURE::UNKNOWN, "the rows were not accepted" );
1561
1562 try
1563 {
1564 recordset.Call( L"Close" );
1565 }
1566 catch( ADO_ERROR& error )
1567 {
1568 applyProviderErrors( session.connection, error );
1569
1570 // A dead connection fails the read, any other failure comes after a complete read
1571 if( classifyFailure( error, error.text ) == DB_FAILURE::UNREACHABLE )
1572 throw;
1573
1574 wxLogTrace( traceDatabase, wxT( "ACE_BACKEND: closing the recordset failed (0x%08lx, %s)" ),
1575 static_cast<unsigned long>( error.hr ), wxString::FromUTF8( error.sqlState ) );
1576 }
1577
1578 pass.rows = delivered;
1579 return {};
1580 },
1581 aSession );
1582
1583 pass.hash = hash.Value();
1584 return pass;
1585}
1586
1587} // namespace
1588
1589
1590ACE_BACKEND::ACE_BACKEND( std::string aPath, std::string aPassword, int aTimeoutSeconds ) :
1591 m_path( std::move( aPath ) ),
1592 m_password( std::move( aPassword ) ),
1593 m_timeout( aTimeoutSeconds ),
1594 m_worker( std::make_unique<DB_WORKER_THREAD>( DB_WORKER_THREAD::SourceKeyFor( m_path ), comThreadInit,
1595 comThreadExit, true, MarkAccessWorkerAbandoned ) )
1596{
1597}
1598
1599
1600std::shared_ptr<ACE_ENGINE_OP> ACE_BACKEND::newOp() const
1601{
1602 return std::make_shared<ACE_ENGINE_OP>( ACE_ENGINE_OP::CLOCK::now() + DB_WORKER_THREAD::CallerBound( m_timeout ) );
1603}
1604
1605
1607{
1608 // Closing the session and the worker's exit hook share the one deadline
1609 auto op = newOp();
1610
1611 dropConnection( op, true );
1612 m_worker->SetShutdownBound( remainingUntil( op->deadline ) );
1613
1614 if( !m_worker->IsHealthy() )
1615 {
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 ) );
1619 }
1620}
1621
1622
1624{
1625 std::wstring progId;
1626
1627 return providerLookup( progId, 0ms );
1628}
1629
1630
1632{
1633 return utf8( _( "still checking for the Microsoft Access Database Engine; try again in a moment" ) );
1634}
1635
1636
1638{
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" ) );
1641
1642 std::optional<PROBE_ANSWER> answer = probeAnswer( 0ms );
1643
1644 if( answer && !answer->error.empty() )
1645 message += " (" + answer->error + ")";
1646
1647 return message;
1648}
1649
1650
1651void ACE_BACKEND::fail( DB_FAILURE aFailure, std::string aError )
1652{
1653 m_lastFailure = aFailure;
1654 m_lastError = ScrubSecret( std::move( aError ), m_password );
1655
1656 wxLogTrace( traceDatabase, wxT( "ACE_BACKEND: %s" ), wxString::FromUTF8( m_lastError ) );
1657}
1658
1659
1661static std::string describeFailure( const std::string& aPath, const CALL_RESULT& aResult,
1662 const std::string& aPassword )
1663{
1664 if( aResult.engineBusy )
1665 return AccessEngineBusyMessage();
1666
1667 const wxString path = wxString::FromUTF8( aPath );
1668
1669 // A short password cannot be scrubbed out of provider text, so none of that text is shown
1670 const std::string redacted = RedactDetail( aResult.detail, aPassword );
1671 const wxString suffix =
1672 redacted.empty() ? wxString() : wxString::Format( wxT( " (%s)" ), wxString::FromUTF8( redacted ) );
1673 wxString message;
1674
1675 switch( aResult.failure )
1676 {
1677 case DB_FAILURE::FILE_NOT_FOUND: return utf8( wxString::Format( _( "database file %s was not found" ), path ) );
1679
1681 message = wxString::Format( _( "database file %s is locked by another user" ), path );
1682 break;
1683
1684 case DB_FAILURE::LOGIN:
1685 message = wxString::Format( _( "the password for database file %s is missing or wrong" ), path );
1686 break;
1687
1689 message = wxString::Format( _( "database file %s could not be reached" ), path );
1690 break;
1691
1692 default: message = wxString::Format( _( "database file %s could not be read" ), path ); break;
1693 }
1694
1695 return utf8( message + suffix );
1696}
1697
1698
1700static bool invalidates( DB_FAILURE aFailure )
1701{
1702 return aFailure == DB_FAILURE::UNREACHABLE || aFailure == DB_FAILURE::FILE_NOT_FOUND;
1703}
1704
1705
1706void ACE_BACKEND::failCall( DB_FAILURE aFailure, std::string aMessage )
1707{
1708 fail( aFailure, std::move( aMessage ) );
1709
1710 if( invalidates( aFailure ) )
1712}
1713
1714
1716{
1717 if( m_connected )
1718 return true;
1719
1720 fail( DB_FAILURE::UNKNOWN, utf8( _( "not connected to a database" ) ) );
1721 return false;
1722}
1723
1724
1725template <typename R>
1726std::optional<R> ACE_BACKEND::runOnWorker( std::function<R( DB_JOB_CONTEXT& )> aJob, const DB_ROW_SINK* aSink,
1727 std::shared_ptr<ACE_ENGINE_OP> aOp,
1728 std::optional<std::chrono::milliseconds> aProgressBound )
1729{
1730 if( !aOp )
1731 aOp = newOp();
1732
1733 m_lastOp = aOp;
1734
1735 auto wrapped = [op = aOp, job = std::move( aJob )]( DB_JOB_CONTEXT& aContext )
1736 {
1737 s_threadOp = op;
1738 return job( aContext );
1739 };
1740
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 );
1743
1744 if( result )
1745 return result;
1746
1747 const DB_WORKER_FAILURE failure = DescribeWorkerOutcome( m_worker->LastOutcome(), m_path );
1748
1750 {
1751 dropConnection( aOp );
1752 }
1754 {
1755 // The stuck thread releases the connection itself when the blocked call returns
1756 m_connected = false;
1757 m_session = 0;
1758 m_cycleSessions.clear();
1759 }
1760
1761 // The operation's own state says whether the abandoned job was stuck on the engine lock
1762 if( aOp->Busy() )
1764 else
1765 fail( failure.failure, failure.message );
1766
1767 return std::nullopt;
1768}
1769
1770
1771void ACE_BACKEND::dropConnection( std::shared_ptr<ACE_ENGINE_OP> aOp, bool aForExit )
1772{
1773 std::vector<uint64_t> ids;
1774
1775 if( m_session )
1776 ids.push_back( m_session );
1777
1778 for( const CYCLE_SESSION& cycleSession : m_cycleSessions )
1779 ids.push_back( cycleSession.id );
1780
1781 m_session = 0;
1782 m_cycleSessions.clear();
1783 m_connected = false;
1784
1785 // An abandoned or dead worker holds the sessions itself, and a thread started to close nothing would wait
1786 // out the stuck one for no gain
1787 if( !m_worker->HasLiveThread() || ( ids.empty() && !aForExit ) )
1788 return;
1789
1790 if( !aOp )
1791 aOp = newOp();
1792
1793 closeOnWorker( std::move( ids ), aOp );
1794}
1795
1796
1797bool ACE_BACKEND::closeOnWorker( std::vector<uint64_t> aIds, std::shared_ptr<ACE_ENGINE_OP> aOp,
1798 std::optional<std::chrono::milliseconds> aBound )
1799{
1800 const bool closed = m_worker->RunCleanup(
1801 [ids = aIds, aOp]()
1802 {
1803 s_threadOp = aOp;
1804
1805 for( uint64_t id : ids )
1806 closeSession( id, *aOp );
1807 },
1808 aBound ? *aBound : remainingUntil( aOp->deadline ) );
1809
1810 // A close that lost the lock leaves its session open, and the operation's state holds that even if the job
1811 // is still running
1812 if( !aIds.empty() && ( aOp->state == ACE_ENGINE_OP::STATE::EXPIRED || ( !closed && aOp->Busy() ) ) )
1814
1815 return closed;
1816}
1817
1818
1820{
1821 m_lastError.clear();
1823
1824 if( m_connected )
1825 return true;
1826
1827 if( m_path.empty() )
1828 {
1829 fail( DB_FAILURE::FILE_NOT_FOUND, utf8( _( "no database file is set" ) ) );
1830 return false;
1831 }
1832
1833 std::wstring progId;
1834
1835 switch( providerLookup( progId, PROBE_WAIT ) )
1836 {
1839 return false;
1840
1842
1845 return false;
1846
1848 break;
1849 }
1850
1851 const uint64_t session = g_nextSession++;
1852
1853 // The same deadline bounds the caller's wait and the job's wait for the engine lock
1854 auto op = newOp();
1855
1856 std::optional<CALL_RESULT> result = runOnWorker<CALL_RESULT>(
1857 [session, progId, path = m_path, password = m_password, timeout = m_timeout, op]( DB_JOB_CONTEXT& )
1858 {
1859 return connectJob( session, progId, path, password, timeout, *op );
1860 },
1861 nullptr, op );
1862
1863 if( !result )
1864 return false;
1865
1866 if( !result->ok )
1867 {
1869 return false;
1870 }
1871
1872 m_session = session;
1873 m_progId = progId;
1874 m_connected = true;
1875 return true;
1876}
1877
1878
1880{
1882}
1883
1884
1885bool ACE_BACKEND::ListTables( std::vector<std::string>& aTables )
1886{
1887 aTables.clear();
1888 m_lastError.clear();
1889
1890 if( !requireConnection() )
1891 return false;
1892
1893 const uint64_t session = m_session;
1894
1895 std::optional<CATALOG_RESULT> result = runOnWorker<CATALOG_RESULT>(
1896 [session]( DB_JOB_CONTEXT& )
1897 {
1898 return listTablesJob( session );
1899 },
1900 nullptr );
1901
1902 if( !result )
1903 return false;
1904
1905 if( !result->call.ok )
1906 {
1907 failCall( result->call.failure, describeFailure( m_path, result->call, m_password ) );
1908 return false;
1909 }
1910
1911 aTables = std::move( result->names );
1912 std::sort( aTables.begin(), aTables.end() );
1913 return true;
1914}
1915
1916
1917std::optional<DB_TABLE_INFO> ACE_BACKEND::DescribeTable( const std::string& aTable )
1918{
1919 // A table that is simply absent leaves no error, so nothing of an earlier failure may remain
1920 m_lastError.clear();
1922
1923 if( !requireConnection() )
1924 return std::nullopt;
1925
1926 const uint64_t session = m_session;
1927
1928 std::optional<CATALOG_RESULT> result = runOnWorker<CATALOG_RESULT>(
1929 [session, aTable]( DB_JOB_CONTEXT& )
1930 {
1931 return describeTableJob( session, aTable );
1932 },
1933 nullptr );
1934
1935 if( !result )
1936 return std::nullopt;
1937
1938 if( !result->call.ok )
1939 {
1940 failCall( result->call.failure, describeFailure( m_path, result->call, m_password ) );
1941 return std::nullopt;
1942 }
1943
1944 if( !result->found )
1945 return std::nullopt;
1946
1948
1949 info.name = std::move( result->table );
1950 info.columns = std::move( result->columns );
1951
1952 return info;
1953}
1954
1955
1956bool ACE_BACKEND::ReadTable( const DB_TABLE_INFO& aTable, const std::vector<std::string>& aColumns, size_t aMaxRows,
1957 std::vector<std::string>& aLabels, const DB_ROW_SINK& aSink )
1958{
1959 aLabels.clear();
1960 m_lastError.clear();
1961
1962 if( !requireConnection() )
1963 return false;
1964
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;
1970
1971 if( aMaxRows == 0 )
1972 {
1973 sql += columns + from + " WHERE 1=0";
1974 }
1975 else if( bounded )
1976 {
1977 const std::string top = "TOP " + std::to_string( aMaxRows ) + " ";
1978
1979 unordered = sql + top + columns + from;
1980 sql += top + columns + from + " ORDER BY 1";
1981 }
1982 else
1983 {
1984 sql += columns + from;
1985 }
1986
1987 return readPass( m_session, sql, unordered, aMaxRows, aLabels, &aSink ).has_value();
1988}
1989
1990
1991std::optional<ACE_BACKEND::PASS_DIGEST>
1992ACE_BACKEND::readPass( uint64_t aSession, const std::string& aSql, const std::string& aUnorderedSql, size_t aMaxRows,
1993 std::vector<std::string>& aLabels, const DB_ROW_SINK* aSink,
1994 std::optional<ACE_ENGINE_OP::CLOCK::time_point> aDeadline )
1995{
1996 static const DB_ROW_SINK discard = []( std::vector<DB_VALUE>&& ) {};
1997
1998 DB_READ_DELIVERY delivery( aLabels, aSink ? *aSink : discard );
1999 auto labels = delivery.Labels();
2000
2001 std::shared_ptr<ACE_ENGINE_OP> op;
2002 std::optional<std::chrono::milliseconds> progressBound;
2003
2004 // A verified pass may wait for the engine only until the read's deadline, but for rows as long as they come
2005 if( aDeadline )
2006 {
2007 op = std::make_shared<ACE_ENGINE_OP>( *aDeadline );
2008 progressBound = DB_WORKER_THREAD::CallerBound( m_timeout );
2009 }
2010
2011 std::optional<PASS_RESULT> result = runOnWorker<PASS_RESULT>(
2012 [aSession, aSql, aUnorderedSql, labels, aMaxRows]( DB_JOB_CONTEXT& aContext )
2013 {
2014 return readTableJob( aContext, aSession, aSql, aUnorderedSql, labels, aMaxRows );
2015 },
2016 delivery.Sink(), op, progressBound );
2017
2018 if( !result )
2019 return std::nullopt;
2020
2021 delivery.Finish();
2022
2023 if( !result->call.ok )
2024 {
2025 aLabels.clear();
2026
2027 if( delivery.SinkThrew() )
2029 else
2030 failCall( result->call.failure, describeFailure( m_path, result->call, m_password ) );
2031
2032 return std::nullopt;
2033 }
2034
2035 return PASS_DIGEST{ result->rows, result->hash, aLabels };
2036}
2037
2038
2040{
2041 // A cycle inside another keeps the stop of the outer one
2042 if( m_cycleDepth++ == 0 )
2043 m_cycleStop = aStopped;
2044}
2045
2046
2048{
2049 if( m_cycleDepth == 0 || --m_cycleDepth > 0 )
2050 return;
2051
2052 m_cycleStop = nullptr;
2053
2054 std::vector<uint64_t> ids;
2055
2056 for( const CYCLE_SESSION& cycleSession : m_cycleSessions )
2057 ids.push_back( cycleSession.id );
2058
2059 m_cycleSessions.clear();
2060
2061 if( ids.empty() )
2062 return;
2063
2064 auto op = newOp();
2065
2066 // A worker stuck in the close still holds the connection, and releases it when the blocked call returns
2067 if( !closeOnWorker( std::move( ids ), op ) )
2068 {
2069 m_connected = false;
2070 m_session = 0;
2071 }
2072}
2073
2074
2076{
2077 return m_cycleStop && m_cycleStop();
2078}
2079
2080
2081void ACE_BACKEND::failStopped( const std::string& aTable )
2082{
2084 utf8( wxString::Format( _( "the read of table %s was stopped" ), wxString::FromUTF8( aTable ) ) ) );
2085}
2086
2087
2088bool ACE_BACKEND::pauseUnlessStopped( std::chrono::milliseconds aWait )
2089{
2090 const ACE_ENGINE_OP::CLOCK::time_point until = ACE_ENGINE_OP::CLOCK::now() + aWait;
2091
2092 // In slices, so a close waits for one slice and not the whole pause
2093 while( !cycleStopped() )
2094 {
2095 const ACE_ENGINE_OP::CLOCK::duration left = until - ACE_ENGINE_OP::CLOCK::now();
2096
2097 if( left <= ACE_ENGINE_OP::CLOCK::duration::zero() )
2098 return true;
2099
2100 std::this_thread::sleep_for( std::min<ACE_ENGINE_OP::CLOCK::duration>( left, 50ms ) );
2101 }
2102
2103 return false;
2104}
2105
2106
2107bool ACE_BACKEND::closeSpentSessions( ACE_ENGINE_OP::CLOCK::time_point aDeadline )
2108{
2109 if( m_cycleSessions.size() < MAX_CYCLE_SESSIONS )
2110 return true;
2111
2112 // Every session has read the table by now, and the newest is kept for the tables after it
2113 const auto spent = m_cycleSessions.end() - ( MAX_CYCLE_SESSIONS - 1 );
2114 std::vector<uint64_t> ids;
2115
2116 for( auto it = m_cycleSessions.begin(); it != spent; ++it )
2117 ids.push_back( it->id );
2118
2119 m_cycleSessions.erase( m_cycleSessions.begin(), spent );
2120
2121 // The read's deadline may be close, and a worker given only what is left of it would be abandoned
2122 if( closeOnWorker( std::move( ids ), std::make_shared<ACE_ENGINE_OP>( aDeadline ),
2124 {
2125 return true;
2126 }
2127
2128 // A worker stuck in the close still holds the connections, and releases them when the blocked call returns
2129 m_connected = false;
2130 m_session = 0;
2131 m_cycleSessions.clear();
2132
2133 if( m_lastError.empty() )
2134 {
2135 const DB_WORKER_FAILURE failure = DescribeWorkerOutcome( m_worker->LastOutcome(), m_path );
2136
2137 fail( failure.failure, failure.message );
2138 }
2139
2140 return false;
2141}
2142
2143
2144std::optional<uint64_t> ACE_BACKEND::freshSession( const std::string& aTable,
2145 ACE_ENGINE_OP::CLOCK::time_point aDeadline,
2146 const std::function<bool()>& aMayStart )
2147{
2148 for( CYCLE_SESSION& cycleSession : m_cycleSessions )
2149 {
2150 if( cycleSession.tables.insert( aTable ).second )
2151 return cycleSession.id;
2152 }
2153
2154 if( !closeSpentSessions( aDeadline ) )
2155 return std::nullopt;
2156
2157 std::minstd_rand jitter( std::random_device{}() );
2158
2159 for( int attempt = 0;; ++attempt )
2160 {
2161 // The close and the pause both take time, in which the read may have been stopped or run out of budget
2162 if( !aMayStart() )
2163 return std::nullopt;
2164
2165 const uint64_t session = g_nextSession++;
2166 auto op = std::make_shared<ACE_ENGINE_OP>( aDeadline );
2167
2168 std::optional<CALL_RESULT> result = runOnWorker<CALL_RESULT>(
2169 [session, progId = m_progId, path = m_path, password = m_password, timeout = m_timeout,
2170 op]( DB_JOB_CONTEXT& )
2171 {
2172 return connectJob( session, progId, path, password, timeout, *op );
2173 },
2174 nullptr, op, DB_WORKER_THREAD::CallerBound( m_timeout ) );
2175
2176 if( !result )
2177 return std::nullopt;
2178
2179 if( result->ok )
2180 {
2181 m_cycleSessions.push_back( CYCLE_SESSION{ session, { aTable } } );
2182
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 ),
2185 m_cycleSessions.size() );
2186 return session;
2187 }
2188
2189 const std::chrono::milliseconds wait = FileInUseBackoff( attempt, jitter );
2190
2191 if( result->failure != DB_FAILURE::FILE_IN_USE || ACE_ENGINE_OP::CLOCK::now() + wait >= aDeadline )
2192 {
2194 return std::nullopt;
2195 }
2196
2197 pauseUnlessStopped( wait );
2198 }
2199}
2200
2201
2202bool ACE_BACKEND::ReadAllRows( const DB_TABLE_INFO& aTable, const std::vector<std::string>& aColumns,
2203 std::vector<std::string>& aLabels, const DB_ROW_SINK& aSink,
2204 const DB_RESTART_SINK& aRestart )
2205{
2206 aLabels.clear();
2207 m_lastError.clear();
2208
2209 if( !requireConnection() )
2210 return false;
2211
2213
2214 const bool read = readVerified( aTable, aColumns, aLabels, aSink, aRestart );
2215
2216 // The cycle's close may fail too, and the read's own failure is the one to report
2217 const std::string error = m_lastError;
2218 const DB_FAILURE failure = m_lastFailure;
2219
2220 EndLoadCycle();
2221
2222 if( !read )
2223 {
2224 aLabels.clear();
2225 m_lastError = error;
2226 m_lastFailure = failure;
2227 }
2228
2229 return read;
2230}
2231
2232
2233bool ACE_BACKEND::readVerified( const DB_TABLE_INFO& aTable, const std::vector<std::string>& aColumns,
2234 std::vector<std::string>& aLabels, const DB_ROW_SINK& aSink,
2235 const DB_RESTART_SINK& aRestart )
2236{
2238
2239 const std::string sql = "SELECT " + selectList( aColumns ) + " FROM " + quoted( aTable.name );
2240 const auto start = CLOCK::now();
2241 const auto bound = DB_WORKER_THREAD::CallerBound( m_timeout );
2242 const wxString table = wxString::FromUTF8( aTable.name );
2243 const wxString path = wxString::FromUTF8( m_path );
2244
2245 // Starts when pass 1 completes, so the first pair always runs and a large table still verifies
2246 std::optional<CLOCK::time_point> retryDeadline;
2247
2248 std::minstd_rand random( std::random_device{}() );
2249 std::uniform_int_distribution<int> pauseMs( 50, 250 );
2250
2251 // No retry starts after the budget, though a pair that finished after it and agreed is still verified
2252 auto mayStart =
2253 [&]()
2254 {
2255 if( cycleStopped() )
2256 {
2257 failStopped( aTable.name );
2258 return false;
2259 }
2260
2261 if( !retryDeadline || CLOCK::now() < *retryDeadline )
2262 return true;
2263
2265 utf8( wxString::Format( _( "table %s in database file %s is being modified by another user" ),
2266 table, path ) ) );
2267 return false;
2268 };
2269
2270 auto timedPass =
2271 [&]( size_t aPass, const DB_ROW_SINK* aPassSink )
2272 {
2273 if( !mayStart() )
2274 return std::optional<PASS_DIGEST>();
2275
2276 const auto passStart = CLOCK::now();
2277
2278 // The first pair waits for the engine a bound from its own start, a retry only to the budget's end
2279 const CLOCK::time_point lockDeadline = retryDeadline ? *retryDeadline : passStart + bound;
2280
2281 std::optional<uint64_t> session = freshSession( aTable.name, lockDeadline, mayStart );
2282
2283 if( !session || !mayStart() )
2284 return std::optional<PASS_DIGEST>();
2285
2286 const auto readStart = CLOCK::now();
2287 std::optional<PASS_DIGEST> digest =
2288 readPass( *session, sql, std::string(), ALL_ROWS, aLabels, aPassSink, lockDeadline );
2289
2290 if( digest )
2291 {
2292 using MS = std::chrono::duration<double, std::milli>;
2293
2294 wxLogTrace( traceDatabase,
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() );
2300 }
2301
2302 return digest;
2303 };
2304
2305 std::optional<PASS_DIGEST> previous = timedPass( 0, nullptr );
2306
2307 if( !previous )
2308 return false;
2309
2310 for( size_t pass = 1;; ++pass )
2311 {
2312 if( pass > 1 )
2313 {
2314 if( !pauseUnlessStopped( std::chrono::milliseconds( pauseMs( random ) ) ) )
2315 {
2316 failStopped( aTable.name );
2317 return false;
2318 }
2319
2320 aRestart();
2321 }
2322
2323 std::optional<PASS_DIGEST> current = timedPass( pass, &aSink );
2324
2325 if( !current )
2326 return false;
2327
2328 if( !retryDeadline )
2329 retryDeadline = CLOCK::now() + bound;
2330
2331 if( *current == *previous )
2332 {
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() );
2335 return true;
2336 }
2337
2338 previous = std::move( current );
2339 }
2340}
int index
const char * name
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.
std::string m_password
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_lastError
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::string m_path
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.
bool Connect() override
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.
DB_FAILURE m_lastFailure
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.
~ACE_BACKEND() override
@ 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)
#define _(s)
void Reset() override
std::chrono::milliseconds FileInUseBackoff(int aAttempt, std::minstd_rand &aRng)
const wxChar *const traceDatabase
Flag to enable database library tracing.
std::string message
std::chrono::steady_clock CLOCK
_OUT_STRING AsString(const std::string &aString)
Definition sexpr.h:131
STL namespace.
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.
Definition ace_backend.h:44
std::chrono::steady_clock CLOCK
Definition ace_backend.h:45
std::atomic< STATE > state
Definition ace_backend.h:68
@ WAITING
A job is blocked on the engine lock.
Definition ace_backend.h:50
@ ACQUIRED
The job took the lock.
Definition ace_backend.h:51
@ EXPIRED
The deadline passed first; never cleared.
Definition ace_backend.h:52
const CLOCK::time_point deadline
Definition ace_backend.h:67
A column as the backend reports it.
std::string name
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)
const std::string table
std::vector< DATABASE_CONNECTION::ROW > rows
std::string path
KIBIS top(path, &reporter)
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.