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 <iostream>
22#include <string_view>
23#include <unordered_set>
24#include <utility>
25#include <wx/datetime.h>
26#include <wx/log.h>
27#include <wx/tokenzr.h>
28
29#include <boost/algorithm/string.hpp>
30
34#include <fmt.h>
35#include <hash.h>
36#include <ki_exception.h>
37#include <lib_symbol.h>
38
39#include "sch_io_database.h"
40
42
43
45 SCH_IO( wxS( "Database library" ) ),
46 m_adapter( nullptr ),
47 m_settings(),
48 m_conn()
49{
51 m_cachePopulated = false;
53}
54
55
59
60
61void SCH_IO_DATABASE::EnumerateSymbolLib( wxArrayString& aSymbolNameList,
62 const wxString& aLibraryPath,
63 const std::map<std::string, UTF8>* aProperties )
64{
65 std::vector<LIB_SYMBOL*> symbols;
66 EnumerateSymbolLib( symbols, aLibraryPath, aProperties );
67
68 for( LIB_SYMBOL* symbol : symbols )
69 aSymbolNameList.Add( symbol->GetName() );
70}
71
72
73void SCH_IO_DATABASE::EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolList,
74 const wxString& aLibraryPath,
75 const std::map<std::string, UTF8>* aProperties )
76{
77 wxCHECK_RET( m_adapter, "Database plugin missing library manager adapter handle!" );
78 ensureSettings( aLibraryPath );
80 cacheLib();
81
82 if( !m_conn )
84
85 bool powerSymbolsOnly = ( aProperties && aProperties->contains( SYMBOL_LIBRARY_ADAPTER::PropPowerSymsOnly ) );
86
87 for( auto const& pair : m_nameToSymbolcache )
88 {
89 LIB_SYMBOL* symbol = pair.second.get();
90
91 if( !powerSymbolsOnly || symbol->IsPower() )
92 aSymbolList.emplace_back( symbol );
93 }
94}
95
96
97LIB_SYMBOL* SCH_IO_DATABASE::LoadSymbol( const wxString& aLibraryPath,
98 const wxString& aAliasName,
99 const std::map<std::string, UTF8>* aProperties )
100{
101 wxCHECK_MSG( m_adapter, nullptr, "Database plugin missing library manager adapter handle!" );
102 ensureSettings( aLibraryPath );
104
105 if( !m_conn )
107
108 cacheLib();
109
110 /*
111 * Table names are tricky, in order to allow maximum flexibility to the user.
112 * The slash character is used as a separator between a table name and symbol name, but symbol
113 * names may also contain slashes and table names may now also be empty (which results in the
114 * slash being dropped in the symbol name when placing a new symbol). So, if a slash is found,
115 * we check if the string before the slash is a valid table name. If not, we assume the table
116 * name is blank if our config has an entry for the null table.
117 */
118
119 std::string tableName;
120 std::string symbolName( aAliasName.ToUTF8() );
121
122 auto sanitizedIt = m_sanitizedNameMap.find( aAliasName );
123
124 if( sanitizedIt != m_sanitizedNameMap.end() )
125 {
126 tableName = sanitizedIt->second.first;
127 symbolName = sanitizedIt->second.second;
128 }
129 else
130 {
131 tableName.clear();
132
133 if( aAliasName.Contains( '/' ) )
134 {
135 tableName = std::string( aAliasName.BeforeFirst( '/' ).ToUTF8() );
136 symbolName = std::string( aAliasName.AfterFirst( '/' ).ToUTF8() );
137 }
138 }
139
140 std::vector<const DATABASE_LIB_TABLE*> tablesToTry;
141
142 for( const DATABASE_LIB_TABLE& tableIter : m_settings->m_Tables )
143 {
144 // no table means globally unique keys, try all tables
145 if( tableName.empty() || tableIter.name == tableName )
146 tablesToTry.emplace_back( &tableIter );
147 }
148
149 if( tablesToTry.empty() )
150 {
151 wxLogTrace( traceDatabase, wxT( "LoadSymbol: table '%s' not found in config" ), tableName );
152 return nullptr;
153 }
154
155 const DATABASE_LIB_TABLE* foundTable = nullptr;
157
158 for( const DATABASE_LIB_TABLE* table : tablesToTry )
159 {
160 if( m_conn->SelectOne( table->table, std::make_pair( table->key_col, symbolName ),
161 result ) )
162 {
163 foundTable = table;
164 wxLogTrace( traceDatabase, wxT( "LoadSymbol: SelectOne (%s, %s) found in %s" ),
165 table->key_col, symbolName, table->table );
166 }
167 else
168 {
169 wxLogTrace( traceDatabase, wxT( "LoadSymbol: SelectOne (%s, %s) failed for table %s" ),
170 table->key_col, symbolName, table->table );
171 }
172 }
173
174 if( !foundTable )
175 return nullptr;
176
177 return loadSymbolFromRow( aAliasName, *foundTable, result ).release();
178}
179
180
181void SCH_IO_DATABASE::GetSubLibraryNames( std::vector<wxString>& aNames )
182{
183 ensureSettings( wxEmptyString );
184
185 aNames.clear();
186
187 std::set<wxString> tableNames;
188
189 for( const DATABASE_LIB_TABLE& tableIter : m_settings->m_Tables )
190 {
191 if( tableNames.count( tableIter.name ) )
192 continue;
193
194 aNames.emplace_back( tableIter.name );
195 tableNames.insert( tableIter.name );
196 }
197}
198
199
200void SCH_IO_DATABASE::GetAvailableSymbolFields( std::vector<wxString>& aNames )
201{
202 std::copy( m_customFields.begin(), m_customFields.end(), std::back_inserter( aNames ) );
203}
204
205
206void SCH_IO_DATABASE::GetDefaultSymbolFields( std::vector<wxString>& aNames )
207{
208 std::copy( m_defaultShownFields.begin(), m_defaultShownFields.end(),
209 std::back_inserter( aNames ) );
210}
211
212
213bool SCH_IO_DATABASE::TestConnection( wxString* aErrorMsg )
214{
215 if( m_conn && m_conn->IsConnected() )
216 return true;
217
218 connect();
219
220 if( aErrorMsg && ( !m_conn || !m_conn->IsConnected() ) )
221 *aErrorMsg = m_lastError;
222
223 return m_conn && m_conn->IsConnected();
224}
225
226
228{
229 // Guard against re-entrant cacheLib() calls. A self-referential symbol row (issue #24249)
230 // causes m_adapter->LoadSymbol to route back into SCH_IO_DATABASE::LoadSymbol, which would
231 // otherwise call cacheLib() again while it is in the middle of populating its caches.
232 if( m_inCacheLib )
233 return;
234
235 long long currentTimestampSeconds = wxDateTime::Now().GetValue().GetValue() / 1000;
236
237 // The materialized LIB_SYMBOL cache is expensive to rebuild for large databases because every
238 // row is duplicated from its source library and has all of its fields processed. Re-check the
239 // database only after max_age has elapsed, and even then rebuild the symbols only if the
240 // underlying row data has actually changed. The global library modify hash must not gate this:
241 // the async library loader bumps it whenever any unrelated library finishes loading, which
242 // would otherwise freeze the symbol chooser for seconds at a time.
244 && ( currentTimestampSeconds - m_cacheTimestamp ) < m_settings->m_Cache.max_age )
245 {
246 return;
247 }
248
249 m_inCacheLib = true;
250
251 struct CACHE_LIB_GUARD
252 {
253 bool* flag;
254 ~CACHE_LIB_GUARD() { *flag = false; }
255 } cacheLibGuard{ &m_inCacheLib };
256
257 // Re-query the database (the connection layer caches results subject to its own max_age) and
258 // compute a lightweight signature of the raw rows so we can skip the costly materialization
259 // when nothing relevant has changed.
260 std::vector<std::pair<const DATABASE_LIB_TABLE*, std::vector<DATABASE_CONNECTION::ROW>>> tableResults;
261 size_t signature = 0;
262
263 for( const DATABASE_LIB_TABLE& table : m_settings->m_Tables )
264 {
265 std::vector<DATABASE_CONNECTION::ROW> results;
266
267 if( !m_conn->SelectAll( table.table, table.key_col, results ) )
268 {
269 if( !m_conn->GetLastError().empty() )
270 {
271 wxString msg = wxString::Format( _( "Error reading database table %s: %s" ),
272 table.table, m_conn->GetLastError() );
273 THROW_IO_ERROR( msg );
274 }
275
276 continue;
277 }
278
279 hash_combine( signature, std::string_view( table.table ) );
280
281 for( const DATABASE_CONNECTION::ROW& result : results )
282 {
283 for( const auto& [column, value] : result )
284 {
285 hash_combine( signature, std::string_view( column ) );
286
287 if( const std::string* str = std::any_cast<std::string>( &value ) )
288 hash_combine( signature, std::string_view( *str ) );
289 }
290
291 // The materialized symbols are duplicated from their source libraries, so fold the
292 // modify hash of each referenced (and loaded) source library into the signature. This
293 // rebuilds the cache when a dependency actually changes - for example when an
294 // asynchronously loaded source library finishes loading and a previously empty
295 // placeholder can now be resolved - without being disturbed by unrelated libraries.
296 if( auto it = result.find( table.symbols_col ); it != result.end() )
297 {
298 if( const std::string* str = std::any_cast<std::string>( &it->second ) )
299 {
300 LIB_ID symbolId;
301 symbolId.Parse( *str );
302
303 if( symbolId.IsValid() )
304 {
305 const UTF8& nickname = symbolId.GetLibNickname();
306 hash_combine( signature, std::string_view( nickname.c_str() ) );
307
308 if( std::optional<int> libHash = m_adapter->GetLibraryModifyHash( nickname ) )
309 hash_combine( signature, *libHash );
310 }
311 }
312 }
313 }
314
315 tableResults.emplace_back( &table, std::move( results ) );
316 }
317
318 if( m_cachePopulated && signature == m_cacheSignature )
319 {
320 // Data is unchanged; just reset the timer so we throttle the next re-check.
321 m_cacheTimestamp = currentTimestampSeconds;
322 return;
323 }
324
325 std::map<wxString, std::unique_ptr<LIB_SYMBOL>> newSymbolCache;
326 std::map<wxString, std::pair<std::string, std::string>> newSanitizedNameMap;
327
328 for( const auto& [table, results] : tableResults )
329 {
330 for( const DATABASE_CONNECTION::ROW& result : results )
331 {
332 if( !result.count( table->key_col ) )
333 continue;
334
335 std::string rawName = std::any_cast<std::string>( result.at( table->key_col ) );
336 UTF8 sanitizedName = LIB_ID::FixIllegalChars( rawName, false );
337 std::string sanitizedKey = sanitizedName.c_str();
338 std::string prefix =
339 ( m_settings->m_GloballyUniqueKeys || table->name.empty() ) ? "" : fmt::format( "{}/", table->name );
340 std::string sanitizedDisplayName = fmt::format( "{}{}", prefix, sanitizedKey );
341 wxString name( sanitizedDisplayName );
342
343 newSanitizedNameMap[name] = std::make_pair( table->name, rawName );
344
345 std::unique_ptr<LIB_SYMBOL> symbol = loadSymbolFromRow( name, *table, result );
346
347 if( symbol )
348 newSymbolCache[symbol->GetName()] = std::move( symbol );
349 }
350 }
351
352 m_nameToSymbolcache = std::move( newSymbolCache );
353 m_sanitizedNameMap = std::move( newSanitizedNameMap );
354
355 m_cacheTimestamp = currentTimestampSeconds;
356 m_cacheSignature = signature;
357 m_cachePopulated = true;
358}
359
360void SCH_IO_DATABASE::ensureSettings( const wxString& aSettingsPath )
361{
362 auto tryLoad =
363 [&]()
364 {
365 if( !m_settings->LoadFromFile() )
366 {
367 wxString msg = wxString::Format(
368 _( "Could not load database library: settings file %s missing or invalid" ),
369 aSettingsPath );
370
371 THROW_IO_ERROR( msg );
372 }
373 };
374
375 if( !m_settings && !aSettingsPath.IsEmpty() )
376 {
377 std::string path( aSettingsPath.ToUTF8() );
378 m_settings = std::make_unique<DATABASE_LIB_SETTINGS>( path );
379 m_settings->SetReadOnly( true );
380
381 tryLoad();
382 }
383 else if( !m_conn && m_settings )
384 {
385 // If we have valid settings but no connection yet; reload settings in case user is editing
386 tryLoad();
387 }
388 else if( m_conn && m_settings && !aSettingsPath.IsEmpty() )
389 {
390 wxASSERT_MSG( aSettingsPath == m_settings->GetFilename(),
391 "Path changed for database library without re-initializing plugin!" );
392 }
393 else if( !m_settings )
394 {
395 wxLogTrace( traceDatabase, wxT( "ensureSettings: no settings but no valid path!" ) );
396 }
397}
398
399
401{
402 wxCHECK_RET( m_settings, "Call ensureSettings before ensureConnection!" );
403
404 connect();
405
406 if( !m_conn || !m_conn->IsConnected() )
407 {
408 wxString msg = wxString::Format(
409 _( "Could not load database library: could not connect to database %s (%s)" ),
410 m_settings->m_Source.dsn, m_lastError );
411
412 THROW_IO_ERROR( msg );
413 }
414}
415
416
418{
419 wxCHECK_RET( m_settings, "Call ensureSettings before connect()!" );
420
421 if( m_conn && !m_conn->IsConnected() )
422 m_conn.reset();
423
424 if( !m_conn )
425 {
426 if( m_settings->m_Source.connection_string.empty() )
427 {
428 m_conn = std::make_unique<DATABASE_CONNECTION>( m_settings->m_Source.dsn,
429 m_settings->m_Source.username,
430 m_settings->m_Source.password,
431 m_settings->m_Source.timeout );
432 }
433 else
434 {
435 std::string cs = m_settings->m_Source.connection_string;
436 std::string basePath( wxFileName( m_settings->GetFilename() ).GetPath().ToUTF8() );
437
438 // Database drivers that use files operate on absolute paths, so provide a mechanism
439 // for specifying on-disk databases that live next to the kicad_dbl file
440 boost::replace_all( cs, "${CWD}", basePath );
441
442 m_conn = std::make_unique<DATABASE_CONNECTION>( cs, m_settings->m_Source.timeout );
443 }
444
445 if( !m_conn->IsConnected() )
446 {
447 m_lastError = m_conn->GetLastError();
448 m_conn.reset();
449 return;
450 }
451
452 for( const DATABASE_LIB_TABLE& tableIter : m_settings->m_Tables )
453 {
454 std::set<std::string> columns;
455
456 columns.insert( boost::to_lower_copy( tableIter.key_col ) );
457 columns.insert( boost::to_lower_copy( tableIter.footprints_col ) );
458 columns.insert( boost::to_lower_copy( tableIter.symbols_col ) );
459
460 columns.insert( boost::to_lower_copy( tableIter.properties.description ) );
461 columns.insert( boost::to_lower_copy( tableIter.properties.footprint_filters ) );
462 columns.insert( boost::to_lower_copy( tableIter.properties.keywords ) );
463 columns.insert( boost::to_lower_copy( tableIter.properties.exclude_from_sim ) );
464 columns.insert( boost::to_lower_copy( tableIter.properties.exclude_from_bom ) );
465 columns.insert( boost::to_lower_copy( tableIter.properties.exclude_from_board ) );
466
467 for( const DATABASE_FIELD_MAPPING& field : tableIter.fields )
468 columns.insert( boost::to_lower_copy( field.column ) );
469
470 m_conn->CacheTableInfo( tableIter.table, columns );
471 }
472
473 m_conn->SetCacheParams( m_settings->m_Cache.max_size, m_settings->m_Cache.max_age );
474 }
475}
476
477
478std::optional<bool> SCH_IO_DATABASE::boolFromAny( const std::any& aVal )
479{
480 try
481 {
482 bool val = std::any_cast<bool>( aVal );
483 return val;
484 }
485 catch( const std::bad_any_cast& )
486 {
487 }
488
489 try
490 {
491 int val = std::any_cast<int>( aVal );
492 return static_cast<bool>( val );
493 }
494 catch( const std::bad_any_cast& )
495 {
496 }
497
498 try
499 {
500 wxString strval( std::any_cast<std::string>( aVal ).c_str(), wxConvUTF8 );
501
502 if( strval.IsEmpty() )
503 return std::nullopt;
504
505 strval.MakeLower();
506
507 for( const auto& trueVal : { wxS( "true" ), wxS( "yes" ), wxS( "y" ), wxS( "1" ) } )
508 {
509 if( strval.Matches( trueVal ) )
510 return true;
511 }
512
513 for( const auto& falseVal : { wxS( "false" ), wxS( "no" ), wxS( "n" ), wxS( "0" ) } )
514 {
515 if( strval.Matches( falseVal ) )
516 return false;
517 }
518 }
519 catch( const std::bad_any_cast& )
520 {
521 }
522
523 return std::nullopt;
524}
525
526
527std::unique_ptr<LIB_SYMBOL> SCH_IO_DATABASE::loadSymbolFromRow( const wxString& aSymbolName,
528 const DATABASE_LIB_TABLE& aTable,
529 const DATABASE_CONNECTION::ROW& aRow )
530{
531 std::unique_ptr<LIB_SYMBOL> symbol = nullptr;
532
533 if( aRow.count( aTable.symbols_col ) )
534 {
535 LIB_SYMBOL* originalSymbol = nullptr;
536
537 // TODO: Support multiple options for symbol
538 std::string symbolIdStr = std::any_cast<std::string>( aRow.at( aTable.symbols_col ) );
539 LIB_ID symbolId;
540 symbolId.Parse( std::any_cast<std::string>( aRow.at( aTable.symbols_col ) ) );
541
542 // A row's Symbols column may resolve back into the same database library (issue #24249,
543 // e.g. a mistyped library nickname). The adapter would route that lookup back into
544 // SCH_IO_DATABASE::LoadSymbol and re-enter loadSymbolFromRow on the same row until the
545 // stack overflows. Track in-flight LIB_IDs and skip the recursive load on re-entry.
546 struct CYCLE_GUARD
547 {
548 std::unordered_set<wxString>* set;
549 wxString key;
550 bool owns = false;
551
552 ~CYCLE_GUARD()
553 {
554 if( owns )
555 set->erase( key );
556 }
557 } guard{ &m_inProgressLoads, {}, false };
558
559 bool cycle = false;
560
561 if( symbolId.IsValid() )
562 {
563 guard.key = symbolId.Format().wx_str();
564 guard.owns = m_inProgressLoads.insert( guard.key ).second;
565 cycle = !guard.owns;
566
567 if( cycle )
568 {
569 wxLogTrace( traceDatabase,
570 wxT( "loadSymbolFromRow: cycle detected resolving '%s' "
571 "(row '%s' in table '%s'); skipping recursive load" ),
572 symbolIdStr, aSymbolName, aTable.name );
573 }
574 else
575 {
576 originalSymbol = m_adapter->LoadSymbol( symbolId );
577 }
578 }
579
580 if( originalSymbol )
581 {
582 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: found original symbol '%s'" ),
583 symbolIdStr );
584 symbol.reset( originalSymbol->Duplicate() );
585 symbol->SetSourceLibId( symbolId );
586 }
587 else if( cycle )
588 {
589 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: source symbol '%s' is a "
590 "self-reference, will create empty symbol" ),
591 symbolIdStr );
592 }
593 else if( !symbolId.IsValid() )
594 {
595 wxLogTrace( traceDatabase, wxT( "loadSymboFromRow: source symbol id '%s' is invalid, "
596 "will create empty symbol" ), symbolIdStr );
597 }
598 else
599 {
600 wxLogTrace( traceDatabase, wxT( "loadSymboFromRow: source symbol '%s' not found, "
601 "will create empty symbol" ), symbolIdStr );
602 }
603 }
604
605 if( !symbol )
606 {
607 // Actual symbol not found: return metadata only; error will be indicated in the
608 // symbol chooser
609 symbol.reset( new LIB_SYMBOL( aSymbolName ) );
610 }
611 else
612 {
613 symbol->SetName( aSymbolName );
614 }
615
616 LIB_ID libId = symbol->GetLibId();
617 libId.SetSubLibraryName( aTable.name );;
618 symbol->SetLibId( libId );
619 wxArrayString footprintsList;
620
621 if( aRow.count( aTable.footprints_col ) )
622 {
623 std::string footprints = std::any_cast<std::string>( aRow.at( aTable.footprints_col ) );
624
625 wxString footprintsStr = wxString( footprints.c_str(), wxConvUTF8 );
626 wxStringTokenizer tokenizer( footprintsStr, ";\t\r\n", wxTOKEN_STRTOK );
627
628 while( tokenizer.HasMoreTokens() )
629 footprintsList.Add( tokenizer.GetNextToken() );
630
631 if( footprintsList.size() > 0 )
632 symbol->GetFootprintField().SetText( footprintsList[0] );
633 }
634 else
635 {
636 wxLogTrace( traceDatabase, wxT( "loadSymboFromRow: footprint field %s not found." ),
637 aTable.footprints_col );
638 }
639
640 if( !aTable.properties.description.empty() && aRow.count( aTable.properties.description ) )
641 {
642 wxString value(
643 std::any_cast<std::string>( aRow.at( aTable.properties.description ) ).c_str(),
644 wxConvUTF8 );
645 symbol->SetDescription( value );
646 }
647
648 if( !aTable.properties.keywords.empty() && aRow.count( aTable.properties.keywords ) )
649 {
650 wxString value( std::any_cast<std::string>( aRow.at( aTable.properties.keywords ) ).c_str(),
651 wxConvUTF8 );
652 symbol->SetKeyWords( value );
653 }
654
655 if( !aTable.properties.footprint_filters.empty()
656 && aRow.count( aTable.properties.footprint_filters ) )
657 {
658 wxString value( std::any_cast<std::string>( aRow.at( aTable.properties.footprint_filters ) )
659 .c_str(),
660 wxConvUTF8 );
661 footprintsList.push_back( value );
662 }
663
664 symbol->SetFPFilters( footprintsList );
665
666 if( !aTable.properties.exclude_from_sim.empty()
667 && aRow.count( aTable.properties.exclude_from_sim ) )
668 {
669 std::optional<bool> val = boolFromAny( aRow.at( aTable.properties.exclude_from_sim ) );
670
671 if( val )
672 {
673 symbol->SetExcludedFromSim( *val );
674 }
675 else
676 {
677 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: exclude_from_sim value for %s "
678 "could not be cast to a boolean" ), aSymbolName );
679 }
680 }
681
682 if( !aTable.properties.exclude_from_board.empty()
683 && aRow.count( aTable.properties.exclude_from_board ) )
684 {
685 std::optional<bool> val = boolFromAny( aRow.at( aTable.properties.exclude_from_board ) );
686
687 if( val )
688 {
689 symbol->SetExcludedFromBoard( *val );
690 }
691 else
692 {
693 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: exclude_from_board value for %s "
694 "could not be cast to a boolean" ), aSymbolName );
695 }
696 }
697
698 if( !aTable.properties.exclude_from_bom.empty()
699 && aRow.count( aTable.properties.exclude_from_bom ) )
700 {
701 std::optional<bool> val = boolFromAny( aRow.at( aTable.properties.exclude_from_bom ) );
702
703 if( val )
704 {
705 symbol->SetExcludedFromBOM( *val );
706 }
707 else
708 {
709 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: exclude_from_bom value for %s "
710 "could not be cast to a boolean" ), aSymbolName );
711 }
712 }
713
714 std::vector<SCH_FIELD*> fields;
715 symbol->GetFields( fields );
716
717 std::unordered_map<wxString, SCH_FIELD*> fieldsMap;
718
719 for( SCH_FIELD* field : fields )
720 fieldsMap[field->GetName()] = field;
721
722 static const wxString c_valueFieldName( wxS( "Value" ) );
723 static const wxString c_datasheetFieldName( wxS( "Datasheet" ) );
724 static const wxString c_footprintFieldName( wxS( "Footprint" ) );
725
726 // User fields produced from the database mapping must appear in the order they are
727 // declared in the .kicad_dbl file, not in lexicographic order of their values. Start
728 // assigning ordinals above whatever the source LIB_SYMBOL already uses so that any
729 // pre-existing user fields keep their relative position before the database fields.
730 int dbFieldOrdinal = symbol->GetNextFieldOrdinal();
731
732 for( const DATABASE_FIELD_MAPPING& mapping : aTable.fields )
733 {
734 if( !aRow.count( mapping.column ) )
735 {
736 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: field %s not found in result" ),
737 mapping.column );
738 continue;
739 }
740
741 // Skip footprint field if it maps to the footprints column, since that column is
742 // already processed above with tokenization for semicolon-separated multiple footprints.
743 if( mapping.name_wx == c_footprintFieldName && mapping.column == aTable.footprints_col )
744 continue;
745
746 std::string strValue;
747
748 try
749 {
750 strValue = std::any_cast<std::string>( aRow.at( mapping.column ) );
751 }
752 catch( std::bad_any_cast& )
753 {
754 }
755
756 wxString value( strValue.c_str(), wxConvUTF8 );
757
758 if( mapping.name_wx == c_valueFieldName )
759 {
760 SCH_FIELD& field = symbol->GetValueField();
761 field.SetText( value );
762
763 if( !mapping.inherit_properties )
764 {
765 field.SetVisible( mapping.visible_on_add );
766 field.SetNameShown( mapping.show_name );
767 }
768 continue;
769 }
770 else if( mapping.name_wx == c_datasheetFieldName )
771 {
772 SCH_FIELD& field = symbol->GetDatasheetField();
773 field.SetText( value );
774
775 if( !mapping.inherit_properties )
776 {
777 field.SetVisible( mapping.visible_on_add );
778 field.SetNameShown( mapping.show_name );
779
780 if( mapping.visible_on_add )
781 field.SetAutoAdded( true );
782 }
783
784 continue;
785 }
786
787 SCH_FIELD* field;
788 bool isNew = false;
789
790 if( fieldsMap.count( mapping.name_wx ) )
791 {
792 field = fieldsMap[mapping.name_wx];
793 }
794 else
795 {
796 field = new SCH_FIELD( nullptr, FIELD_T::USER );
797 field->SetName( mapping.name_wx );
798 isNew = true;
799 fieldsMap[mapping.name_wx] = field;
800 }
801
802 // Assign a sort-order ordinal so the property editor and BOM see the fields in the
803 // order declared in the .kicad_dbl file. Without this, all USER fields share the
804 // same FIELD_T::USER id and fall through to value-based comparison in operator<.
805 // SetOrdinal forces m_id to FIELD_T::USER, so only apply it to non-mandatory fields
806 // - a DB mapping that lands on a mandatory field by name (e.g. Reference or
807 // Description) must keep its FIELD_T identity for downstream lookups.
808 if( !field->IsMandatory() )
809 field->SetOrdinal( dbFieldOrdinal++ );
810
811 if( !mapping.inherit_properties || isNew )
812 {
813 field->SetVisible( mapping.visible_on_add );
814 field->SetAutoAdded( true );
815 field->SetNameShown( mapping.show_name );
816 }
817
818 field->SetText( value );
819
820 if( isNew )
821 symbol->AddDrawItem( field, false );
822
823 m_customFields.insert( mapping.name_wx );
824
825 if( mapping.visible_in_chooser )
826 m_defaultShownFields.insert( mapping.name_wx );
827 }
828
829 symbol->GetDrawItems().sort();
830
831 // Field mappings (including Description) are applied with SCH_FIELD::SetText, which does not
832 // refresh the cached values the library tree and chooser read. Without this the upper chooser
833 // panel keeps the source symbol's description while the details panel shows the database value.
834 symbol->RefreshLibraryTreeCaches();
835
836 return symbol;
837}
838
839
841{
842 return new DIALOG_DATABASE_LIB_SETTINGS( aParent, this );
843}
const char * name
std::map< std::string, std::any > ROW
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition dialog_shim.h:65
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:381
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:48
bool IsValid() const
Check if this LID_ID is valid.
Definition lib_id.h:168
UTF8 Format() const
Definition lib_id.cpp:115
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:188
Define a library symbol object.
Definition lib_symbol.h:79
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:93
void SetOrdinal(int aOrdinal)
Definition sch_field.h:138
bool IsMandatory() const
void SetAutoAdded(bool aAutoAdded)
Definition sch_field.h:237
void SetName(const wxString &aName)
void SetText(const wxString &aText) override
void SetNameShown(bool aShown=true)
Definition sch_field.h:219
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.
std::unordered_set< wxString > m_inProgressLoads
LIB_IDs whose resolution is in flight, used to break self-referential cycles where a row's Symbols co...
std::unique_ptr< DATABASE_CONNECTION > m_conn
Generally will be null if no valid connection is established.
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)
bool m_cachePopulated
True once the LIB_SYMBOL cache has been materialized at least once.
bool TestConnection(wxString *aErrorMsg=nullptr)
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...
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)
SYMBOL_LIBRARY_ADAPTER * m_adapter
long long m_cacheTimestamp
DIALOG_SHIM * CreateConfigurationDialog(wxWindow *aParent) override
void GetAvailableSymbolFields(std::vector< wxString > &aNames) override
Retrieves a list of (custom) field names that are present on symbols in this library.
std::unique_ptr< LIB_SYMBOL > loadSymbolFromRow(const wxString &aSymbolName, const DATABASE_LIB_TABLE &aTable, const DATABASE_CONNECTION::ROW &aRow)
std::set< wxString > m_customFields
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...
SCH_IO(const wxString &aName)
Definition sch_io.h:375
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)
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
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
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
@ USER
The field ID hasn't been set yet; field is invalid.
std::string path
std::vector< std::vector< std::string > > table
wxString result
Test unit parsing edge cases and error handling.