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