KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sch_io_database.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 (C) 2022 Jon Evans <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <chrono>
22#include <iostream>
23#include <string_view>
24#include <set>
25#include <utility>
26#include <bs_thread_pool.hpp>
27#include <wx/datetime.h>
28#include <wx/log.h>
29#include <wx/tokenzr.h>
30
31#include <boost/algorithm/string.hpp>
32#include <json_common.h>
33#include <pin_map.h>
34
38#include <fmt.h>
39#include <hash.h>
40#include <ki_exception.h>
41#include <lib_symbol.h>
42
43#include "sch_io_database.h"
44
46
47
48thread_local std::unordered_set<wxString> SCH_IO_DATABASE::s_inProgressLoads;
49
50
52 SCH_IO( wxS( "Database library" ) ),
53 m_adapter( nullptr ),
54 m_settings(),
55 m_conn()
56{
59}
60
61
66
67
68void SCH_IO_DATABASE::EnumerateSymbolLib( wxArrayString& aSymbolNameList,
69 const wxString& aLibraryPath,
70 const std::map<std::string, UTF8>* aProperties )
71{
72 std::vector<LIB_SYMBOL*> symbols;
73 EnumerateSymbolLib( symbols, aLibraryPath, aProperties );
74
75 for( LIB_SYMBOL* symbol : symbols )
76 {
77 aSymbolNameList.Add( symbol->GetName() );
78 delete symbol;
79 }
80}
81
82
83void SCH_IO_DATABASE::EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolList,
84 const wxString& aLibraryPath,
85 const std::map<std::string, UTF8>* aProperties )
86{
87 wxCHECK_RET( m_adapter, "Database plugin missing library manager adapter handle!" );
88 ensureSettings( aLibraryPath );
90 cacheLib();
91
92 if( !m_conn )
94
95 bool powerSymbolsOnly = ( aProperties && aProperties->contains( SYMBOL_LIBRARY_ADAPTER::PropPowerSymsOnly ) );
96
97 std::shared_lock lock( m_cacheMutex );
98
99 for( auto const& pair : m_nameToSymbolcache )
100 {
101 LIB_SYMBOL* symbol = pair.second.get();
102
103 if( !powerSymbolsOnly || symbol->IsPower() )
104 aSymbolList.emplace_back( symbol->Duplicate() );
105 }
106}
107
108
109void SCH_IO_DATABASE::CheckLibrary( const wxString& aLibraryPath,
110 const std::map<std::string, UTF8>* aProperties )
111{
112 ensureSettings( aLibraryPath );
114}
115
116
117LIB_SYMBOL* SCH_IO_DATABASE::LoadSymbol( const wxString& aLibraryPath,
118 const wxString& aAliasName,
119 const std::map<std::string, UTF8>* aProperties )
120{
121 wxCHECK_MSG( m_adapter, nullptr, "Database plugin missing library manager adapter handle!" );
122 ensureSettings( aLibraryPath );
124
125 if( !m_conn )
127
128 cacheLib();
129
130 std::string tableName;
131 std::string symbolName( aAliasName.ToUTF8() );
132
133 {
134 std::shared_lock lock( m_cacheMutex );
135
136 if( auto cacheIt = m_nameToSymbolcache.find( aAliasName ); cacheIt != m_nameToSymbolcache.end() )
137 {
138 LIB_SYMBOL* cached = cacheIt->second.get();
139 return cached->Duplicate();
140 }
141
142 auto sanitizedIt = m_sanitizedNameMap.find( aAliasName );
143
144 if( sanitizedIt != m_sanitizedNameMap.end() )
145 {
146 tableName = sanitizedIt->second.first;
147 symbolName = sanitizedIt->second.second;
148 }
149 }
150
151 /*
152 * Table names are tricky, in order to allow maximum flexibility to the user.
153 * The slash character is used as a separator between a table name and symbol name, but symbol
154 * names may also contain slashes and table names may now also be empty (which results in the
155 * slash being dropped in the symbol name when placing a new symbol). So, if a slash is found,
156 * we check if the string before the slash is a valid table name. If not, we assume the table
157 * name is blank if our config has an entry for the null table.
158 */
159
160 if( tableName.empty() && aAliasName.Contains( '/' ) )
161 {
162 tableName = std::string( aAliasName.BeforeFirst( '/' ).ToUTF8() );
163 symbolName = std::string( aAliasName.AfterFirst( '/' ).ToUTF8() );
164 }
165
166 std::vector<const DATABASE_LIB_TABLE*> tablesToTry;
167
168 for( const DATABASE_LIB_TABLE& tableIter : m_settings->m_Tables )
169 {
170 // no table means globally unique keys, try all tables
171 if( tableName.empty() || tableIter.name == tableName )
172 tablesToTry.emplace_back( &tableIter );
173 }
174
175 if( tablesToTry.empty() )
176 {
177 wxLogTrace( traceDatabase, wxT( "LoadSymbol: table '%s' not found in config" ), tableName );
178 return nullptr;
179 }
180
181 const DATABASE_LIB_TABLE* foundTable = nullptr;
183
184 for( const DATABASE_LIB_TABLE* table : tablesToTry )
185 {
186 if( m_conn->SelectOne( table->table, std::make_pair( table->key_col, symbolName ), result ) )
187 {
188 foundTable = table;
189 wxLogTrace( traceDatabase, wxT( "LoadSymbol: SelectOne (%s, %s) found in %s" ),
190 table->key_col, symbolName, table->table );
191 }
192 else
193 {
194 wxLogTrace( traceDatabase, wxT( "LoadSymbol: SelectOne (%s, %s) failed for table %s" ),
195 table->key_col, symbolName, table->table );
196 }
197 }
198
199 if( !foundTable )
200 return nullptr;
201
202 return loadSymbolFromRow( aAliasName, *foundTable, result ).release();
203}
204
205
206void SCH_IO_DATABASE::GetSubLibraryNames( std::vector<wxString>& aNames )
207{
208 ensureSettings( wxEmptyString );
209
210 aNames.clear();
211
212 std::set<wxString> tableNames;
213
214 for( const DATABASE_LIB_TABLE& tableIter : m_settings->m_Tables )
215 {
216 if( tableNames.count( tableIter.name ) )
217 continue;
218
219 aNames.emplace_back( tableIter.name );
220 tableNames.insert( tableIter.name );
221 }
222}
223
224
225void SCH_IO_DATABASE::GetAvailableSymbolFields( std::vector<wxString>& aNames )
226{
227 std::lock_guard<std::mutex> lock( m_symbolLoadMutex );
228
229 std::copy( m_customFields.begin(), m_customFields.end(), std::back_inserter( aNames ) );
230}
231
232
233void SCH_IO_DATABASE::GetDefaultSymbolFields( std::vector<wxString>& aNames )
234{
235 std::lock_guard<std::mutex> lock( m_symbolLoadMutex );
236
237 std::copy( m_defaultShownFields.begin(), m_defaultShownFields.end(), std::back_inserter( aNames ) );
238}
239
240
241size_t SCH_IO_DATABASE::computeSignature( const TABLE_RESULT_LIST& aTableResults ) const
242{
243 size_t signature = 0;
244
245 for( const auto& [table, results] : aTableResults )
246 {
247 for( const DATABASE_CONNECTION::ROW& result : results )
248 {
249 size_t rowSignature = 0;
250
251 for( const auto& [column, value] : result )
252 {
253 hash_combine( signature, std::string_view( column ) );
254
255 if( const std::string* str = std::any_cast<std::string>( &value ) )
256 hash_combine( rowSignature, std::string_view( *str ) );
257 }
258
259 hash_combine( signature, rowSignature );
260
261 if( auto it = result.find( table->symbols_col ); it != result.end() )
262 {
263 if( const std::string* str = std::any_cast<std::string>( &it->second ) )
264 {
265 LIB_ID symbolId;
266 symbolId.Parse( *str );
267
268 if( symbolId.IsValid() )
269 {
270 const UTF8& nickname = symbolId.GetLibNickname();
271 hash_combine( signature, std::string_view( nickname.c_str() ) );
272
273 if( std::optional<int> libHash = m_adapter->GetLibraryModifyHash( nickname ) )
274 hash_combine( signature, *libHash );
275 }
276 }
277 }
278 }
279 }
280
281 return signature;
282}
283
284
286{
287 if( !m_adapter )
288 return nullptr;
289
290 if( !m_previewMode )
291 return m_adapter->LoadSymbol( aId );
292
293 std::optional<LIBRARY_TABLE_ROW*> row = m_adapter->GetRow( aId.GetLibNickname() );
294
295 // A database library would query its own tables, which the preview must not do
296 if( row && SCH_IO_MGR::EnumFromStr( ( *row )->Type() ) == SCH_IO_MGR::SCH_DATABASE )
297 {
298 reportIssue( PREVIEW_ISSUE::FIELD::SYMBOLS,
299 wxString::Format( _( "Symbol '%s' is in a database library and is not previewed." ),
300 aId.Format().wx_str() ),
302 return nullptr;
303 }
304
305 // Not memoized; the pointer belongs to another library's cache, which a later load may rebuild
306 LIB_SYMBOL* symbol = m_adapter->LoadSymbol( aId );
307
308 if( !symbol )
309 {
310 reportIssue( PREVIEW_ISSUE::FIELD::SYMBOLS,
311 wxString::Format( _( "Symbol '%s' was not found." ), aId.Format().wx_str() ),
313 }
314
315 return symbol;
316}
317
318
319std::unique_ptr<DATABASE_CONNECTION> SCH_IO_DATABASE::CreateConnection( const DATABASE_LIB_SETTINGS& aSettings,
320 bool aConnectNow, const wxString& aBaseDir )
321{
322 const DATABASE_SOURCE& source = aSettings.m_Source;
323
324 if( source.connection_string.empty() )
325 {
326 return std::make_unique<DATABASE_CONNECTION>( source.dsn, source.username, source.password,
327 source.timeout, aConnectNow );
328 }
329
330 std::string cs = source.connection_string;
331 wxString baseDir = aBaseDir.IsEmpty() ? wxFileName( aSettings.GetFilename() ).GetPath() : aBaseDir;
332 std::string basePath( baseDir.ToUTF8() );
333
334 // Database drivers that use files operate on absolute paths, so provide a mechanism
335 // for specifying on-disk databases that live next to the kicad_dbl file
336 boost::replace_all( cs, "${CWD}", basePath );
337
338 return std::make_unique<DATABASE_CONNECTION>( cs, source.timeout, aConnectNow );
339}
340
341
342wxString SCH_IO_DATABASE::SymbolNameForRow( const DATABASE_LIB_TABLE& aTable, bool aGloballyUniqueKeys,
343 const DATABASE_CONNECTION::ROW& aRow, std::string* aRawKey )
344{
345 auto it = aRow.find( aTable.key_col );
346
347 if( it == aRow.end() )
348 return wxEmptyString;
349
350 std::string rawName = std::any_cast<std::string>( it->second );
351 UTF8 sanitizedName = LIB_ID::FixIllegalChars( rawName, false );
352 std::string prefix = ( aGloballyUniqueKeys || aTable.name.empty() ) ? "" : fmt::format( "{}/", aTable.name );
353
354 if( aRawKey )
355 *aRawKey = rawName;
356
357 return wxString( fmt::format( "{}{}", prefix, sanitizedName.c_str() ) );
358}
359
360
361std::vector<std::unique_ptr<LIB_SYMBOL>>
363 bool aGloballyUniqueKeys, const std::vector<DATABASE_CONNECTION::ROW>& aRows,
364 PREVIEW_ISSUES* aIssues )
365{
366 SCH_IO_DATABASE preview;
367 preview.m_adapter = aLibs;
368 preview.m_previewIssues = aIssues;
369 preview.m_previewMode = true;
370
371 std::vector<std::unique_ptr<LIB_SYMBOL>> symbols;
372 std::map<wxString, std::pair<size_t, std::string>> listed;
373
374 for( const DATABASE_CONNECTION::ROW& row : aRows )
375 {
376 std::string rawKey;
377 wxString name = SymbolNameForRow( aTable, aGloballyUniqueKeys, row, &rawKey );
378
379 if( name.IsEmpty() )
380 continue;
381
382 try
383 {
384 std::unique_ptr<LIB_SYMBOL> symbol = preview.loadSymbolFromRow( name, aTable, row );
385
386 if( !symbol )
387 continue;
388
389 // The library keeps the last row listed under a name, so the preview does the same
390 if( auto it = listed.find( name ); it != listed.end() )
391 {
392 if( it->second.second == rawKey )
393 {
394 preview.reportIssue( PREVIEW_ISSUE::FIELD::REPEATED_KEY,
395 wxString::Format( _( "Key '%s' appears in more than one row; only the last "
396 "row is kept." ),
397 wxString::FromUTF8( rawKey ) ),
399 }
400 else
401 {
402 preview.reportIssue( PREVIEW_ISSUE::FIELD::KEY,
403 wxString::Format( _( "Keys '%s' and '%s' are both listed as '%s'; only the "
404 "last is kept." ),
405 wxString::FromUTF8( it->second.second ),
406 wxString::FromUTF8( rawKey ), name ),
408 }
409
410 symbols[it->second.first] = std::move( symbol );
411 it->second.second = rawKey;
412 continue;
413 }
414
415 listed[name] = { symbols.size(), rawKey };
416 symbols.push_back( std::move( symbol ) );
417 }
418 catch( const IO_ERROR& e )
419 {
420 preview.reportIssue( PREVIEW_ISSUE::FIELD::KEY, e.What(), RPT_SEVERITY_ERROR );
421 }
422 }
423
424 return symbols;
425}
426
427
428void SCH_IO_DATABASE::reportIssue( PREVIEW_ISSUE::FIELD aField, const wxString& aMessage, SEVERITY aSeverity ) const
429{
430 if( m_previewIssues )
431 m_previewIssues->push_back( { aField, aMessage } );
432 else
433 Report( aMessage, aSeverity );
434}
435
436
438 std::map<wxString, std::unique_ptr<LIB_SYMBOL>>& aSymbolCache,
439 std::map<wxString, std::pair<std::string, std::string>>& aSanitizedNameMap )
440{
441 for( const auto& [table, results] : aTableResults )
442 {
443 for( const DATABASE_CONNECTION::ROW& result : results )
444 {
445 std::string rawName;
446 wxString name = SymbolNameForRow( *table, m_settings->m_GloballyUniqueKeys, result, &rawName );
447
448 if( name.IsEmpty() )
449 continue;
450
451 aSanitizedNameMap[name] = std::make_pair( table->name, rawName );
452
453 std::unique_ptr<LIB_SYMBOL> symbol = loadSymbolFromRow( name, *table, result );
454
455 if( symbol )
456 aSymbolCache[symbol->GetName()] = std::move( symbol );
457 }
458 }
459
460 return !aSymbolCache.empty();
461}
462
463
465{
466 // Guard against re-entrant cacheLib() calls. A self-referential symbol row (issue #24249)
467 // causes m_adapter->LoadSymbol to route back into SCH_IO_DATABASE::LoadSymbol, which would
468 // otherwise call cacheLib() again while it is in the middle of populating its caches.
469 if( m_inCacheLib )
470 return;
471
472 long long currentTimestampSeconds = wxDateTime::Now().GetValue().GetValue() / 1000;
473
474 // After the initial load, the background refresh thread handles all cache updates.
475 {
476 std::shared_lock lock( m_cacheMutex );
477
478 if( m_cachePopulated )
479 return;
480 }
481
482 m_inCacheLib = true;
483
484 struct CACHE_LIB_GUARD
485 {
486 bool* flag;
487 ~CACHE_LIB_GUARD() { *flag = false; }
488 } cacheLibGuard{ &m_inCacheLib };
489
490 // Re-query the database (the connection layer caches results subject to its own max_age) and
491 // compute a lightweight signature of the raw rows so we can skip the costly materialization
492 // when nothing relevant has changed.
493 TABLE_RESULT_LIST tableResults;
494
495 for( const DATABASE_LIB_TABLE& table : m_settings->m_Tables )
496 {
497 std::vector<DATABASE_CONNECTION::ROW> results;
498
499 if( !m_conn->SelectAll( table.table, table.key_col, results ) )
500 {
501 if( !m_conn->GetLastError().empty() )
502 THROW_IO_ERRORF( _( "Error reading database table %s: %s" ), table.table, m_conn->GetLastError() );
503
504 continue;
505 }
506
507 tableResults.emplace_back( &table, std::move( results ) );
508 }
509
510 size_t signature = computeSignature( tableResults );
511
512 {
513 std::unique_lock lock( m_cacheMutex );
514
515 if( m_cachePopulated && signature == m_cacheSignature )
516 {
517 m_cacheTimestamp = currentTimestampSeconds;
518 return;
519 }
520 }
521
522 std::map<wxString, std::unique_ptr<LIB_SYMBOL>> newSymbolCache;
523 std::map<wxString, std::pair<std::string, std::string>> newSanitizedNameMap;
524
525 materializeCache( tableResults, newSymbolCache, newSanitizedNameMap );
526
527 {
528 std::unique_lock lock( m_cacheMutex );
529
530 m_nameToSymbolcache = std::move( newSymbolCache );
531 m_sanitizedNameMap = std::move( newSanitizedNameMap );
532
533 m_cacheTimestamp = currentTimestampSeconds;
534 m_cacheSignature = signature;
535 m_cachePopulated = true;
536 m_modifyHash++;
537 }
538
539 if( !m_refreshRunning.load() )
541}
542
543
545{
546 if( m_refreshRunning.exchange( true ) )
547 return;
548
549 wxLogTrace( traceDatabase, wxT( "Starting background refresh thread" ) );
551}
552
553
555{
556 m_refreshRunning = false;
557 m_refreshCV.notify_all();
558
559 if( m_refreshThread.joinable() )
560 m_refreshThread.join();
561}
562
563
565{
566 BS::this_thread::set_os_thread_name( "dblib bg" );
567
568 while( m_refreshRunning.load() )
569 {
570 long long maxAge = 0;
571
572 {
573 std::unique_lock lock( m_cacheMutex );
574
575 if( m_settings )
576 maxAge = m_settings->m_Cache.max_age;
577 }
578
579 if( maxAge <= 0 )
580 maxAge = 1;
581
582 std::shared_lock connGuard( m_cacheMutex );
583
584 if( m_conn && m_cachePopulated.load() )
585 {
586 wxLogTrace( traceDatabase, wxT( "Initiating background refresh" ) );
587
588 try
589 {
590 std::vector<std::pair<const DATABASE_LIB_TABLE*, std::vector<DATABASE_CONNECTION::ROW>>> tableResults;
591 bool querySuccess = true;
592
593 for( const DATABASE_LIB_TABLE& table : m_settings->m_Tables )
594 {
595 m_conn->ClearCache( table.table );
596
597 std::vector<DATABASE_CONNECTION::ROW> results;
598
599 if( !m_conn->SelectAll( table.table, table.key_col, results ) )
600 {
601 wxLogTrace( traceDatabase, wxT( "Background refresh: SelectAll failed for table %s" ),
602 table.table );
603 querySuccess = false;
604 break;
605 }
606
607 tableResults.emplace_back( &table, std::move( results ) );
608 }
609
610 if( querySuccess )
611 {
612 size_t signature = computeSignature( tableResults );
613
614 bool dataChanged = false;
615
616 {
617 // Upgrade to unique lock for cache mutation
618 connGuard.unlock();
619 std::unique_lock lock( m_cacheMutex );
620
621 if( signature == m_cacheSignature )
622 m_cacheTimestamp = wxDateTime::Now().GetValue().GetValue() / 1000;
623 else
624 dataChanged = true;
625 }
626
627 if( dataChanged )
628 {
629 std::map<wxString, std::unique_ptr<LIB_SYMBOL>> newSymbolCache;
630 std::map<wxString, std::pair<std::string, std::string>> newSanitizedNameMap;
631
632 materializeCache( tableResults, newSymbolCache, newSanitizedNameMap );
633
634 wxLogTrace( traceDatabase, wxT( "Background refresh: new data" ) );
635
636 {
637 std::unique_lock lock( m_cacheMutex );
638
639 m_nameToSymbolcache = std::move( newSymbolCache );
640 m_sanitizedNameMap = std::move( newSanitizedNameMap );
641
642 m_cacheTimestamp = wxDateTime::Now().GetValue().GetValue() / 1000;
643 m_cacheSignature = signature;
644 m_cachePopulated = true;
645 m_modifyHash++;
646 }
647 }
648 else
649 {
650 wxLogTrace( traceDatabase, wxT( "Background refresh: no new data" ) );
651 }
652 }
653 }
654 catch( const IO_ERROR& e )
655 {
656 wxLogTrace( traceDatabase, wxT( "Background refresh failed: %s; cache preserved" ), e.What() );
657 }
658 catch( const std::exception& e )
659 {
660 wxLogTrace( traceDatabase, wxT( "Background refresh failed: %s; cache preserved" ), e.what() );
661 }
662 }
663
664 {
665 std::unique_lock lock( m_refreshMutex );
666 m_refreshCV.wait_for( lock, std::chrono::seconds( maxAge ),
667 [this]()
668 {
669 return !m_refreshRunning.load();
670 } );
671 }
672 }
673}
674
675
676void SCH_IO_DATABASE::ensureSettings( const wxString& aSettingsPath )
677{
678 auto tryLoad =
679 [&]()
680 {
681 if( !m_settings->LoadFromFile() )
682 {
683 THROW_IO_ERRORF( _( "Could not load database library: settings file %s missing or invalid" ),
684 aSettingsPath );
685 }
686
687 m_settings->ResolvePassword();
688 };
689
690 if( !m_settings && !aSettingsPath.IsEmpty() )
691 {
692 std::string path( aSettingsPath.ToUTF8() );
693 m_settings = std::make_unique<DATABASE_LIB_SETTINGS>( path );
694 m_settings->SetReadOnly( true );
695
696 tryLoad();
697 }
698 else if( !m_conn && m_settings )
699 {
700 // If we have valid settings but no connection yet; reload settings in case user is editing
701 tryLoad();
702 }
703 else if( m_conn && m_settings && !aSettingsPath.IsEmpty() )
704 {
705 wxASSERT_MSG( aSettingsPath == m_settings->GetFilename(),
706 "Path changed for database library without re-initializing plugin!" );
707 }
708 else if( !m_settings )
709 {
710 wxLogTrace( traceDatabase, wxT( "ensureSettings: no settings but no valid path!" ) );
711 }
712}
713
714
716{
717 wxCHECK_RET( m_settings, "Call ensureSettings before ensureConnection!" );
718
719 connect();
720
721 if( !m_conn || !m_conn->IsConnected() )
722 {
723 THROW_IO_ERRORF( _( "Could not load database library: could not connect to database %s (%s)" ),
724 m_settings->m_Source.dsn, m_lastError );
725 }
726}
727
728
730{
731 wxCHECK_RET( m_settings, "Call ensureSettings before connect()!" );
732
733 {
734 std::unique_lock connLock( m_cacheMutex );
735
736 if( m_conn && !m_conn->IsConnected() )
737 m_conn.reset();
738 }
739
740 if( !m_conn )
741 {
742 std::unique_ptr<DATABASE_CONNECTION> newConn = CreateConnection( *m_settings );
743
744 if( !newConn->IsConnected() )
745 {
746 m_lastError = newConn->GetLastError();
747 return;
748 }
749
750 for( const DATABASE_LIB_TABLE& tableIter : m_settings->m_Tables )
751 {
752 // Trusted even if unreported by the driver, to support generated columns (#16952)
753 std::set<std::string> requiredColumns{ tableIter.key_col,
754 tableIter.footprints_col,
755 tableIter.symbols_col };
756
757 for( const DATABASE_FIELD_MAPPING& field : tableIter.fields )
758 requiredColumns.insert( field.column );
759
760 if( !tableIter.pins_col.empty() )
761 requiredColumns.insert( tableIter.pins_col );
762
763 // Only used if confirmed present, so a misconfigured mapping can't break the table (#23532)
764 std::set<std::string> optionalColumns{ tableIter.properties.description,
766 tableIter.properties.keywords,
769 tableIter.properties.exclude_from_board };
770
771 newConn->CacheTableInfo( tableIter.table, requiredColumns, optionalColumns );
772 }
773
774 newConn->SetCacheParams( m_settings->m_Cache.max_size, m_settings->m_Cache.max_age );
775
776 std::unique_lock connLock( m_cacheMutex );
777 m_conn = std::move( newConn );
778 }
779}
780
781
782std::optional<bool> SCH_IO_DATABASE::boolFromAny( const std::any& aVal )
783{
784 try
785 {
786 bool val = std::any_cast<bool>( aVal );
787 return val;
788 }
789 catch( const std::bad_any_cast& )
790 {
791 }
792
793 try
794 {
795 int val = std::any_cast<int>( aVal );
796 return static_cast<bool>( val );
797 }
798 catch( const std::bad_any_cast& )
799 {
800 }
801
802 try
803 {
804 wxString strval( std::any_cast<std::string>( aVal ).c_str(), wxConvUTF8 );
805
806 if( strval.IsEmpty() )
807 return std::nullopt;
808
809 strval.MakeLower();
810
811 for( const auto& trueVal : { wxS( "true" ), wxS( "yes" ), wxS( "y" ), wxS( "1" ) } )
812 {
813 if( strval.Matches( trueVal ) )
814 return true;
815 }
816
817 for( const auto& falseVal : { wxS( "false" ), wxS( "no" ), wxS( "n" ), wxS( "0" ) } )
818 {
819 if( strval.Matches( falseVal ) )
820 return false;
821 }
822 }
823 catch( const std::bad_any_cast& )
824 {
825 }
826
827 return std::nullopt;
828}
829
830
831std::unique_ptr<LIB_SYMBOL> SCH_IO_DATABASE::loadSymbolFromRow( const wxString& aSymbolName,
832 const DATABASE_LIB_TABLE& aTable,
833 const DATABASE_CONNECTION::ROW& aRow )
834{
835 std::unique_ptr<LIB_SYMBOL> symbol = nullptr;
836
837 if( aRow.contains( aTable.symbols_col ) )
838 {
839 std::string symbols = std::any_cast<std::string>( aRow.at( aTable.symbols_col ) );
840 wxString symbolsStr = wxString( symbols.c_str(), wxConvUTF8 );
841 wxStringTokenizer tokenizer( symbolsStr, ";\t\r\n", wxTOKEN_STRTOK );
842
843 std::vector<LIB_ID> symbolIds;
844
845 while( tokenizer.HasMoreTokens() )
846 {
847 wxString token = tokenizer.GetNextToken();
848 LIB_ID id;
849 id.Parse( std::string( token.ToUTF8() ) );
850
851 if( id.IsValid() )
852 {
853 symbolIds.push_back( id );
854 }
855 else
856 {
857 reportIssue( PREVIEW_ISSUE::FIELD::SYMBOLS,
858 wxString::Format( _( "'%s' is not a symbol; expected Library:Symbol." ), token ),
860 }
861 }
862
863 // A row's Symbols column may resolve back into the same database library (issue #24249,
864 // e.g. a mistyped library nickname). The adapter would route that lookup back into
865 // SCH_IO_DATABASE::LoadSymbol and re-enter loadSymbolFromRow on the same row until the
866 // stack overflows. Track in-flight LIB_IDs and skip the recursive load on re-entry.
867 std::vector<LIB_SYMBOL*> sourceSymbols;
868 std::vector<wxString> sourceSymbolNames;
869
870 for( const LIB_ID& symbolId : symbolIds )
871 {
872 wxString symbolIdStr = symbolId.Format().wx_str();
873
874 if( !s_inProgressLoads.insert( symbolIdStr ).second )
875 {
876 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: cycle detected resolving '%s' "
877 "(row '%s' in table '%s'); skipping recursive load" ),
878 symbolIdStr, aSymbolName, aTable.name );
879 continue;
880 }
881
882 struct CYCLE_GUARD
883 {
884 std::unordered_set<wxString>* set;
885 wxString key;
886 ~CYCLE_GUARD() { set->erase( key ); }
887 } guard{ &s_inProgressLoads, symbolIdStr };
888
889 LIB_SYMBOL* src = nullptr;
890
891 {
892 std::lock_guard lock( m_symbolLoadMutex );
893 src = resolveSourceSymbol( symbolId );
894 }
895
896 if( src )
897 {
898 sourceSymbols.push_back( src );
899 sourceSymbolNames.push_back( src->GetName() );
900 }
901 else
902 {
903 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: source symbol '%s' not found" ), symbolIdStr );
904 }
905 }
906
907 if( sourceSymbols.empty() )
908 {
909 // Actual symbol not found: return metadata only; error will be indicated in the
910 // symbol chooser
911 symbol.reset( new LIB_SYMBOL( aSymbolName ) );
912 }
913 else if( sourceSymbols.size() == 1 )
914 {
915 symbol.reset( sourceSymbols[0]->Duplicate() );
916 symbol->SetSourceLibId( symbolIds[0] );
917 symbol->SetName( aSymbolName );
918 }
919 else
920 {
921 // If the database row specifies multiple symbols, put together a composite,
922 // but since source symbols may already have multiple body styles, we
923 // need to flatten out any common to all body styles items on source symbols.
924 // Since we don't support varying all properties, take those from the first one.
925
926 symbol.reset( new LIB_SYMBOL( aSymbolName ) );
927 symbol->SetSourceLibId( symbolIds[0] );
928
929 symbol->SetUnitCount( sourceSymbols[0]->GetUnitCount(), false );
930 symbol->SetPowerSymbolProp( sourceSymbols[0]->IsPower() );
931 symbol->SetShowPinNames( sourceSymbols[0]->GetShowPinNames() );
932 symbol->SetShowPinNumbers( sourceSymbols[0]->GetShowPinNumbers() );
933 symbol->SetPinNameOffset( sourceSymbols[0]->GetPinNameOffset() );
934
935 for( FIELD_T fieldId : MANDATORY_FIELDS )
936 {
937 if( SCH_FIELD* srcField = sourceSymbols[0]->GetField( fieldId ) )
938 {
939 SCH_FIELD* dstField = symbol->GetField( fieldId );
940 *dstField = *srcField;
941 dstField->SetParent( symbol.get() );
942 }
943 }
944
945 std::vector<wxString> bodyStyleNames;
946 std::vector<int> sourceBodyStyleCounts;
947 std::vector<int> compositeBodyStyleBase;
948
949 int nextBodyStyle = 1;
950
951 for( size_t i = 0; i < sourceSymbols.size(); ++i )
952 {
953 int srcCount = std::max( 1, sourceSymbols[i]->GetBodyStyleCount() );
954 sourceBodyStyleCounts.push_back( srcCount );
955 compositeBodyStyleBase.push_back( nextBodyStyle );
956
957 if( srcCount == 1 )
958 {
959 bodyStyleNames.push_back( sourceSymbolNames[i] );
960 }
961 else
962 {
963 for( int style = 1; style <= srcCount; ++style )
964 {
965 wxString styleName = sourceSymbols[i]->GetBodyStyleDescription( style, false );
966 bodyStyleNames.push_back( sourceSymbolNames[i] + wxT( " (" ) + styleName + wxT( ")" ) );
967 }
968 }
969
970 nextBodyStyle += srcCount;
971 }
972
973 int totalBodyStyles = nextBodyStyle - 1;
974
975 symbol->SetHasDeMorganBodyStyles( false );
976 symbol->SetBodyStyleNames( bodyStyleNames );
977
978 std::set<wxString> mergedFieldNames;
979
980 for( size_t i = 0; i < sourceSymbols.size(); ++i )
981 {
982 std::unique_ptr<LIB_SYMBOL> srcSymbol = sourceSymbols[i]->Flatten();
983 int srcCount = sourceBodyStyleCounts[i];
984 int base = compositeBodyStyleBase[i];
985
986 for( SCH_ITEM& item : srcSymbol->GetDrawItems() )
987 {
988 if( item.Type() == SCH_FIELD_T )
989 {
990 SCH_FIELD& field = static_cast<SCH_FIELD&>( item );
991
992 if( field.IsMandatory() || !mergedFieldNames.insert( field.GetName() ).second )
993 continue;
994
995 int targetStyle;
996
997 if( item.GetBodyStyle() == ALL_BODY_STYLES )
998 targetStyle = base;
999 else
1000 targetStyle = base + ( item.GetBodyStyle() - 1 );
1001
1002 if( targetStyle > totalBodyStyles )
1003 continue;
1004
1005 SCH_ITEM* newItem = item.Duplicate( IGNORE_PARENT_GROUP );
1006 newItem->SetParent( symbol.get() );
1007 newItem->SetBodyStyle( targetStyle );
1008 symbol->AddDrawItem( newItem, false );
1009 }
1010 else if( item.GetBodyStyle() == ALL_BODY_STYLES )
1011 {
1012 for( int bodyStyle = 0; bodyStyle < srcCount; ++bodyStyle )
1013 {
1014 SCH_ITEM* newItem = item.Duplicate( IGNORE_PARENT_GROUP );
1015 newItem->SetParent( symbol.get() );
1016 newItem->SetBodyStyle( base + bodyStyle );
1017 symbol->AddDrawItem( newItem, false );
1018 }
1019 }
1020 else
1021 {
1022 int targetStyle = base + ( item.GetBodyStyle() - 1 );
1023
1024 if( targetStyle > totalBodyStyles )
1025 continue;
1026
1027 SCH_ITEM* newItem = item.Duplicate( IGNORE_PARENT_GROUP );
1028 newItem->SetParent( symbol.get() );
1029 newItem->SetBodyStyle( targetStyle );
1030 symbol->AddDrawItem( newItem, false );
1031 }
1032 }
1033 }
1034
1035 symbol->GetDrawItems().sort();
1036 }
1037 }
1038 else
1039 {
1040 symbol.reset( new LIB_SYMBOL( aSymbolName ) );
1041 }
1042
1043 LIB_ID libId = symbol->GetLibId();
1044 libId.SetSubLibraryName( aTable.name );
1045 symbol->SetLibId( libId );
1046
1047 wxArrayString footprintsList;
1048
1049 if( aRow.count( aTable.footprints_col ) )
1050 {
1051 std::string footprints = std::any_cast<std::string>( aRow.at( aTable.footprints_col ) );
1052
1053 wxString footprintsStr = wxString( footprints.c_str(), wxConvUTF8 );
1054 wxStringTokenizer tokenizer( footprintsStr, ";\t\r\n", wxTOKEN_STRTOK );
1055
1056 while( tokenizer.HasMoreTokens() )
1057 footprintsList.Add( tokenizer.GetNextToken() );
1058
1059 if( footprintsList.size() > 0 )
1060 symbol->GetFootprintField().SetText( footprintsList[0] );
1061 }
1062 else
1063 {
1064 wxLogTrace( traceDatabase, wxT( "loadSymboFromRow: footprint field %s not found." ), aTable.footprints_col );
1065 }
1066
1067 // Pin-to-pad maps (issue #2282): attach non-destructively. The pins column carries either the
1068 // spec-form named object { "pin_maps": [...], "associated_footprints": [...] } or the legacy
1069 // flat MR !2540 array (read for one release, bound to the row's footprints).
1070 if( !aTable.pins_col.empty() && aRow.count( aTable.pins_col ) )
1071 {
1072 try
1073 {
1074 std::string jsonStr = std::any_cast<std::string>( aRow.at( aTable.pins_col ) );
1075
1076 if( !jsonStr.empty() )
1077 {
1078 nlohmann::json json = nlohmann::json::parse( jsonStr );
1079
1080 if( json.is_object() && json.contains( "pin_maps" ) )
1081 {
1082 symbol->SetPinMaps( ParsePinMapSet( json ) );
1083 symbol->SetAssociatedFootprints( ParseAssociatedFootprints( json ) );
1084 }
1085 else if( !footprintsList.IsEmpty() )
1086 {
1087 std::unordered_map<wxString, std::vector<wxString>> assignments = ParseLegacyPinAssignments( json );
1088
1089 if( !assignments.empty() )
1090 {
1091 const wxString mapName = wxS( "Database" );
1092
1093 symbol->PinMaps().AddOrReplace( MakeLegacyPinMap( mapName, assignments ) );
1094
1095 // Bind the one named map to every footprint the row offers, mirroring the
1096 // old behaviour where the assignment applied regardless of footprint.
1097 std::vector<ASSOCIATED_FOOTPRINT> associations;
1098
1099 for( const wxString& footprint : footprintsList )
1100 {
1101 LIB_ID fpId;
1102 fpId.Parse( footprint );
1103 associations.push_back( { fpId, mapName } );
1104 }
1105
1106 symbol->SetAssociatedFootprints( std::move( associations ) );
1107 }
1108 }
1109 }
1110 }
1111 catch( const std::exception& e )
1112 {
1113 // Surface a malformed pin-map payload to the user instead of silently dropping it; the
1114 // symbol still loads without the map (issue #2282).
1115 reportIssue( PREVIEW_ISSUE::FIELD::PIN_MAP,
1116 wxString::Format( _( "Error parsing pin map for database symbol '%s': %s" ), aSymbolName,
1117 e.what() ),
1119 }
1120 }
1121
1122 if( !aTable.properties.description.empty() && aRow.count( aTable.properties.description ) )
1123 {
1124 wxString value(
1125 std::any_cast<std::string>( aRow.at( aTable.properties.description ) ).c_str(),
1126 wxConvUTF8 );
1127 symbol->SetDescription( value );
1128 }
1129
1130 if( !aTable.properties.keywords.empty() && aRow.count( aTable.properties.keywords ) )
1131 {
1132 wxString value( std::any_cast<std::string>( aRow.at( aTable.properties.keywords ) ).c_str(),
1133 wxConvUTF8 );
1134 symbol->SetKeyWords( value );
1135 }
1136
1137 if( !aTable.properties.footprint_filters.empty()
1138 && aRow.count( aTable.properties.footprint_filters ) )
1139 {
1140 wxString value( std::any_cast<std::string>( aRow.at( aTable.properties.footprint_filters ) )
1141 .c_str(),
1142 wxConvUTF8 );
1143 footprintsList.push_back( value );
1144 }
1145
1146 symbol->SetFPFilters( footprintsList );
1147
1148 if( !aTable.properties.exclude_from_sim.empty()
1149 && aRow.count( aTable.properties.exclude_from_sim ) )
1150 {
1151 std::optional<bool> val = boolFromAny( aRow.at( aTable.properties.exclude_from_sim ) );
1152
1153 if( val )
1154 {
1155 symbol->SetExcludedFromSim( *val );
1156 }
1157 else
1158 {
1159 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: exclude_from_sim value for %s "
1160 "could not be cast to a boolean" ), aSymbolName );
1161 }
1162 }
1163
1164 if( !aTable.properties.exclude_from_board.empty()
1165 && aRow.count( aTable.properties.exclude_from_board ) )
1166 {
1167 std::optional<bool> val = boolFromAny( aRow.at( aTable.properties.exclude_from_board ) );
1168
1169 if( val )
1170 {
1171 symbol->SetExcludedFromBoard( *val );
1172 }
1173 else
1174 {
1175 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: exclude_from_board value for %s "
1176 "could not be cast to a boolean" ), aSymbolName );
1177 }
1178 }
1179
1180 if( !aTable.properties.exclude_from_bom.empty()
1181 && aRow.count( aTable.properties.exclude_from_bom ) )
1182 {
1183 std::optional<bool> val = boolFromAny( aRow.at( aTable.properties.exclude_from_bom ) );
1184
1185 if( val )
1186 {
1187 symbol->SetExcludedFromBOM( *val );
1188 }
1189 else
1190 {
1191 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: exclude_from_bom value for %s "
1192 "could not be cast to a boolean" ), aSymbolName );
1193 }
1194 }
1195
1196 std::vector<SCH_FIELD*> fields;
1197 symbol->GetFields( fields );
1198
1199 std::unordered_map<wxString, SCH_FIELD*> fieldsMap;
1200
1201 for( SCH_FIELD* field : fields )
1202 fieldsMap[field->GetName()] = field;
1203
1204 static const wxString c_valueFieldName( wxS( "Value" ) );
1205 static const wxString c_datasheetFieldName( wxS( "Datasheet" ) );
1206 static const wxString c_footprintFieldName( wxS( "Footprint" ) );
1207
1208 // User fields produced from the database mapping must appear in the order they are
1209 // declared in the .kicad_dbl file, not in lexicographic order of their values. Start
1210 // assigning ordinals above whatever the source LIB_SYMBOL already uses so that any
1211 // pre-existing user fields keep their relative position before the database fields.
1212 int dbFieldOrdinal = symbol->GetNextFieldOrdinal();
1213
1214 for( const DATABASE_FIELD_MAPPING& mapping : aTable.fields )
1215 {
1216 if( !aRow.count( mapping.column ) )
1217 {
1218 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: field %s not found in result" ), mapping.column );
1219 continue;
1220 }
1221
1222 // Skip footprint field if it maps to the footprints column, since that column is
1223 // already processed above with tokenization for semicolon-separated multiple footprints.
1224 if( mapping.name_wx == c_footprintFieldName && mapping.column == aTable.footprints_col )
1225 continue;
1226
1227 std::string strValue;
1228
1229 try
1230 {
1231 strValue = std::any_cast<std::string>( aRow.at( mapping.column ) );
1232 }
1233 catch( std::bad_any_cast& )
1234 {
1235 }
1236
1237 wxString value( strValue.c_str(), wxConvUTF8 );
1238
1239 if( mapping.name_wx == c_valueFieldName )
1240 {
1241 SCH_FIELD& field = symbol->GetValueField();
1242 field.SetText( value );
1243
1244 if( !mapping.inherit_properties )
1245 {
1246 field.SetVisible( mapping.visible_on_add );
1247 field.SetNameShown( mapping.show_name );
1248 }
1249
1250 continue;
1251 }
1252 else if( mapping.name_wx == c_datasheetFieldName )
1253 {
1254 SCH_FIELD& field = symbol->GetDatasheetField();
1255 field.SetText( value );
1256
1257 if( !mapping.inherit_properties )
1258 {
1259 field.SetVisible( mapping.visible_on_add );
1260 field.SetNameShown( mapping.show_name );
1261
1262 if( mapping.visible_on_add )
1263 field.SetAutoAdded( true );
1264 }
1265
1266 continue;
1267 }
1268
1269 SCH_FIELD* field;
1270 bool isNew = false;
1271
1272 if( fieldsMap.count( mapping.name_wx ) )
1273 {
1274 field = fieldsMap[mapping.name_wx];
1275 }
1276 else
1277 {
1278 field = new SCH_FIELD( nullptr, FIELD_T::USER );
1279 field->SetName( mapping.name_wx );
1280 isNew = true;
1281 fieldsMap[mapping.name_wx] = field;
1282 }
1283
1284 // Assign a sort-order ordinal so the property editor and BOM see the fields in the
1285 // order declared in the .kicad_dbl file. Without this, all USER fields share the
1286 // same FIELD_T::USER id and fall through to value-based comparison in operator<.
1287 // NB: a DB mapping that lands on a mandatory field by name (e.g. Reference or
1288 // Description) must keep its FIELD_T identity for downstream lookups.
1289 field->SetOrdinal( dbFieldOrdinal++, field->IsMandatory() ? field->GetId() : FIELD_T::USER );
1290
1291 if( !mapping.inherit_properties || isNew )
1292 {
1293 field->SetVisible( mapping.visible_on_add );
1294 field->SetAutoAdded( true );
1295 field->SetNameShown( mapping.show_name );
1296 }
1297
1298 field->SetText( value );
1299
1300 if( isNew )
1301 symbol->AddDrawItem( field, false );
1302
1303 m_customFields.insert( mapping.name_wx );
1304
1305 if( mapping.visible_in_chooser )
1306 m_defaultShownFields.insert( mapping.name_wx );
1307 }
1308
1309 symbol->GetDrawItems().sort();
1310
1311 // Field mappings (including Description) are applied with SCH_FIELD::SetText, which does not
1312 // refresh the cached values the library tree and chooser read. Without this the upper chooser
1313 // panel keeps the source symbol's description while the details panel shows the database value.
1314 symbol->RefreshLibraryTreeCaches();
1315
1316 return symbol;
1317}
1318
1319
1320DIALOG_SHIM* SCH_IO_DATABASE::CreateConfigurationDialog( wxWindow* aParent, const wxString& aLibraryPath )
1321{
1322 return new DIALOG_DATABASE_LIB_SETTINGS( aParent, aLibraryPath, m_adapter );
1323}
const char * name
std::map< std::string, std::any > ROW
Edits every setting of a .kicad_dbl file and previews the resulting symbols.
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition dialog_shim.h:80
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:153
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:347
virtual void Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) const
Definition io_base.cpp:124
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
virtual const char * what() const override
std::exception interface, returned as UTF-8
wxString GetFilename() const
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:65
bool IsValid() const
Check if this LID_ID is valid.
Definition lib_id.h:168
UTF8 Format() const
Definition lib_id.cpp:132
void SetSubLibraryName(const UTF8 &aName)
Definition lib_id.h:127
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition lib_id.h:83
static UTF8 FixIllegalChars(const UTF8 &aLibItemName, bool aLib)
Replace illegal LIB_ID item name characters with underscores '_'.
Definition lib_id.cpp:205
Define a library symbol object.
Definition lib_symbol.h:114
bool IsPower() const override
virtual LIB_SYMBOL * Duplicate() const
Create a copy of a LIB_SYMBOL and assigns unique KIIDs to the copy and its children.
Definition lib_symbol.h:130
wxString GetName() const override
Definition lib_symbol.h:183
bool IsMandatory() const
void SetOrdinal(int aOrdinal, FIELD_T aType)
Definition sch_field.h:149
FIELD_T GetId() const
Definition sch_field.h:143
void SetAutoAdded(bool aAutoAdded)
Definition sch_field.h:248
wxString GetName(bool aUseDefaultName=true) const
Return the field name (not translated).
void SetName(const wxString &aName)
void SetText(const wxString &aText) override
void SetNameShown(bool aShown=true)
Definition sch_field.h:230
void reportIssue(PREVIEW_ISSUE::FIELD aField, const wxString &aMessage, SEVERITY aSeverity) const
Reports to the preview's issue list when there is one, otherwise to the reporter.
static std::unique_ptr< DATABASE_CONNECTION > CreateConnection(const DATABASE_LIB_SETTINGS &aSettings, bool aConnectNow=true, const wxString &aBaseDir=wxEmptyString)
Opens a connection described by aSettings, expanding ${CWD} in a connection string to the directory h...
void EnumerateSymbolLib(wxArrayString &aSymbolNameList, const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Populate a list of LIB_SYMBOL alias names contained within the library aLibraryPath.
DIALOG_SHIM * CreateConfigurationDialog(wxWindow *aParent, const wxString &aLibraryPath) override
std::shared_mutex m_cacheMutex
PREVIEW_ISSUES * m_previewIssues
Collects the problems found while building a preview; null outside BuildPreviewSymbols()
std::unique_ptr< DATABASE_CONNECTION > m_conn
Generally will be null if no valid connection is established.
static wxString SymbolNameForRow(const DATABASE_LIB_TABLE &aTable, bool aGloballyUniqueKeys, const DATABASE_CONNECTION::ROW &aRow, std::string *aRawKey=nullptr)
void GetDefaultSymbolFields(std::vector< wxString > &aNames) override
Retrieves a list of (custom) field names that should be shown by default for this library in the symb...
void ensureSettings(const wxString &aSettingsPath)
static thread_local std::unordered_set< wxString > s_inProgressLoads
LIB_IDs whose resolution is in flight on the calling thread, used to break self-referential cycles wh...
static std::vector< std::unique_ptr< LIB_SYMBOL > > BuildPreviewSymbols(SYMBOL_LIBRARY_ADAPTER *aLibs, const DATABASE_LIB_TABLE &aTable, bool aGloballyUniqueKeys, const std::vector< DATABASE_CONNECTION::ROW > &aRows, PREVIEW_ISSUES *aIssues)
Builds symbols from already-fetched rows exactly as the library would name and load them,...
std::atomic< bool > m_cachePopulated
True once the LIB_SYMBOL cache has been materialized at least once.
bool m_inCacheLib
Re-entrancy guard for cacheLib(), tripped when a self-referential load routes back through the adapte...
std::map< wxString, std::unique_ptr< LIB_SYMBOL > > m_nameToSymbolcache
std::map< wxString, std::pair< std::string, std::string > > m_sanitizedNameMap
std::set< wxString > m_defaultShownFields
virtual ~SCH_IO_DATABASE()
size_t m_cacheSignature
Signature of the raw database rows at last materialization; used to skip rebuilding the LIB_SYMBOL ca...
std::vector< std::pair< const DATABASE_LIB_TABLE *, std::vector< DATABASE_CONNECTION::ROW > > > TABLE_RESULT_LIST
void GetSubLibraryNames(std::vector< wxString > &aNames) override
Retrieves a list of sub-libraries in this library.
std::unique_ptr< DATABASE_LIB_SETTINGS > m_settings
static std::optional< bool > boolFromAny(const std::any &aVal)
size_t computeSignature(const TABLE_RESULT_LIST &aTableResults) const
void CheckLibrary(const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Validate that the library at aLibraryPath is reachable and well-formed, without necessarily loading s...
SYMBOL_LIBRARY_ADAPTER * m_adapter
std::thread m_refreshThread
long long m_cacheTimestamp
std::condition_variable m_refreshCV
void GetAvailableSymbolFields(std::vector< wxString > &aNames) override
Retrieves a list of (custom) field names that are present on symbols in this library.
std::atomic< bool > m_refreshRunning
bool m_previewMode
Set on the throwaway instance behind BuildPreviewSymbols()
bool materializeCache(const TABLE_RESULT_LIST &aTableResults, std::map< wxString, std::unique_ptr< LIB_SYMBOL > > &aSymbolCache, std::map< wxString, std::pair< std::string, std::string > > &aSanitizedNameMap)
std::unique_ptr< LIB_SYMBOL > loadSymbolFromRow(const wxString &aSymbolName, const DATABASE_LIB_TABLE &aTable, const DATABASE_CONNECTION::ROW &aRow)
std::set< wxString > m_customFields
std::mutex m_symbolLoadMutex
LIB_SYMBOL * resolveSourceSymbol(const LIB_ID &aId)
Resolves one entry of a row's symbols column through the library adapter.
std::vector< PREVIEW_ISSUE > PREVIEW_ISSUES
LIB_SYMBOL * LoadSymbol(const wxString &aLibraryPath, const wxString &aAliasName, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load a LIB_SYMBOL object having aPartName from the aLibraryPath containing a library format that this...
std::mutex m_refreshMutex
static SCH_FILE_T EnumFromStr(const wxString &aFileType)
Return the file type from the corresponding plugin type name: "kicad", "legacy", etc.
SCH_IO(const wxString &aName)
Definition sch_io.h:406
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
SCH_ITEM * Duplicate(bool addToParentGroup, SCH_COMMIT *aCommit=nullptr, bool aDoClone=false) const
Routine to create a new copy of given item.
Definition sch_item.cpp:191
virtual void SetBodyStyle(int aBodyStyle)
Definition sch_item.h:252
An interface to the global shared library manager that is schematic-specific and linked to one projec...
static const char * PropPowerSymsOnly
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
Definition utf8.h:67
const char * c_str() const
Definition utf8.h:104
wxString wx_str() const
Definition utf8.cpp:41
const char *const traceDatabase
#define _(s)
#define IGNORE_PARENT_GROUP
Definition eda_item.h:55
nlohmann::json json
Definition gerbview.cpp:50
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_IO_ERRORF(msg,...)
static constexpr void hash_combine(std::size_t &seed)
This is a dummy function to take the final case of hash_combine below.
Definition hash.h:28
std::string source
PIN_MAP MakeLegacyPinMap(const wxString &aName, const std::unordered_map< wxString, std::vector< wxString > > &aAssignments)
Build a single named PIN_MAP from a legacy symbol-pin to footprint-pad(s) assignment table (the flat ...
Definition pin_map.cpp:151
std::vector< ASSOCIATED_FOOTPRINT > ParseAssociatedFootprints(const nlohmann::json &aParent)
Parse the spec-form footprint associations from aParent["associated_footprints"] (issue #2282).
Definition pin_map.cpp:254
std::unordered_map< wxString, std::vector< wxString > > ParseLegacyPinAssignments(const nlohmann::json &aArray)
Parse the legacy flat pin-assignment JSON array (MR !2540 form) into a symbol-pin to footprint-pad(s)...
Definition pin_map.cpp:187
PIN_MAP_SET ParsePinMapSet(const nlohmann::json &aParent)
Parse the spec-form named maps from aParent["pin_maps"] into a PIN_MAP_SET (issue #2282).
Definition pin_map.cpp:221
SEVERITY
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
static std::string strValue(double aValue)
bool visible_in_chooser
Whether the column is shown by default in the chooser.
std::string column
Database column name.
bool inherit_properties
Whether or not to inherit properties from symbol field.
bool visible_on_add
Whether to show the field when placing the symbol.
bool show_name
Whether or not to show the field name as well as its value.
wxString name_wx
KiCad field name (converted)
A database library table will be mapped to a sub-library provided by the database library entry in th...
std::string key_col
Unique key column name (will form part of the LIB_ID)
std::string name
KiCad library nickname (will form part of the LIB_ID)
std::string symbols_col
Column name containing KiCad symbol refs.
std::string footprints_col
Column name containing KiCad footprint refs.
std::vector< DATABASE_FIELD_MAPPING > fields
std::string table
Database table to pull content from.
MAPPABLE_SYMBOL_PROPERTIES properties
std::string pins_col
Column name containing JSON pin assignments (optional)
#define ALL_BODY_STYLES
Definition symbol.h:151
#define MANDATORY_FIELDS
FIELD_T
The set of all field indices assuming an array like sequence that a SCH_COMPONENT or LIB_PART can hol...
@ USER
The field ID hasn't been set yet; field is invalid.
std::string path
wxString result
Test unit parsing edge cases and error handling.
@ SCH_FIELD_T
Definition typeinfo.h:146