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
49 SCH_IO( wxS( "Database library" ) ),
50 m_adapter( nullptr ),
51 m_settings(),
52 m_conn()
53{
56}
57
58
63
64
65void SCH_IO_DATABASE::EnumerateSymbolLib( wxArrayString& aSymbolNameList,
66 const wxString& aLibraryPath,
67 const std::map<std::string, UTF8>* aProperties )
68{
69 std::vector<LIB_SYMBOL*> symbols;
70 EnumerateSymbolLib( symbols, aLibraryPath, aProperties );
71
72 for( LIB_SYMBOL* symbol : symbols )
73 {
74 aSymbolNameList.Add( symbol->GetName() );
75 delete symbol;
76 }
77}
78
79
80void SCH_IO_DATABASE::EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolList,
81 const wxString& aLibraryPath,
82 const std::map<std::string, UTF8>* aProperties )
83{
84 wxCHECK_RET( m_adapter, "Database plugin missing library manager adapter handle!" );
85 ensureSettings( aLibraryPath );
87 cacheLib();
88
89 if( !m_conn )
91
92 bool powerSymbolsOnly = ( aProperties && aProperties->contains( SYMBOL_LIBRARY_ADAPTER::PropPowerSymsOnly ) );
93
94 std::shared_lock lock( m_cacheMutex );
95
96 for( auto const& pair : m_nameToSymbolcache )
97 {
98 LIB_SYMBOL* symbol = pair.second.get();
99
100 if( !powerSymbolsOnly || symbol->IsPower() )
101 aSymbolList.emplace_back( symbol->Duplicate() );
102 }
103}
104
105
106void SCH_IO_DATABASE::CheckLibrary( const wxString& aLibraryPath,
107 const std::map<std::string, UTF8>* aProperties )
108{
109 ensureSettings( aLibraryPath );
111}
112
113
114LIB_SYMBOL* SCH_IO_DATABASE::LoadSymbol( const wxString& aLibraryPath,
115 const wxString& aAliasName,
116 const std::map<std::string, UTF8>* aProperties )
117{
118 wxCHECK_MSG( m_adapter, nullptr, "Database plugin missing library manager adapter handle!" );
119 ensureSettings( aLibraryPath );
121
122 if( !m_conn )
124
125 cacheLib();
126
127 std::string tableName;
128 std::string symbolName( aAliasName.ToUTF8() );
129
130 {
131 std::shared_lock lock( m_cacheMutex );
132
133 if( auto cacheIt = m_nameToSymbolcache.find( aAliasName ); cacheIt != m_nameToSymbolcache.end() )
134 {
135 LIB_SYMBOL* cached = cacheIt->second.get();
136 return cached->Duplicate();
137 }
138
139 auto sanitizedIt = m_sanitizedNameMap.find( aAliasName );
140
141 if( sanitizedIt != m_sanitizedNameMap.end() )
142 {
143 tableName = sanitizedIt->second.first;
144 symbolName = sanitizedIt->second.second;
145 }
146 }
147
148 /*
149 * Table names are tricky, in order to allow maximum flexibility to the user.
150 * The slash character is used as a separator between a table name and symbol name, but symbol
151 * names may also contain slashes and table names may now also be empty (which results in the
152 * slash being dropped in the symbol name when placing a new symbol). So, if a slash is found,
153 * we check if the string before the slash is a valid table name. If not, we assume the table
154 * name is blank if our config has an entry for the null table.
155 */
156
157 if( tableName.empty() && aAliasName.Contains( '/' ) )
158 {
159 tableName = std::string( aAliasName.BeforeFirst( '/' ).ToUTF8() );
160 symbolName = std::string( aAliasName.AfterFirst( '/' ).ToUTF8() );
161 }
162
163 std::vector<const DATABASE_LIB_TABLE*> tablesToTry;
164
165 for( const DATABASE_LIB_TABLE& tableIter : m_settings->m_Tables )
166 {
167 // no table means globally unique keys, try all tables
168 if( tableName.empty() || tableIter.name == tableName )
169 tablesToTry.emplace_back( &tableIter );
170 }
171
172 if( tablesToTry.empty() )
173 {
174 wxLogTrace( traceDatabase, wxT( "LoadSymbol: table '%s' not found in config" ), tableName );
175 return nullptr;
176 }
177
178 const DATABASE_LIB_TABLE* foundTable = nullptr;
180
181 for( const DATABASE_LIB_TABLE* table : tablesToTry )
182 {
183 if( m_conn->SelectOne( table->table, std::make_pair( table->key_col, symbolName ), result ) )
184 {
185 foundTable = table;
186 wxLogTrace( traceDatabase, wxT( "LoadSymbol: SelectOne (%s, %s) found in %s" ),
187 table->key_col, symbolName, table->table );
188 }
189 else
190 {
191 wxLogTrace( traceDatabase, wxT( "LoadSymbol: SelectOne (%s, %s) failed for table %s" ),
192 table->key_col, symbolName, table->table );
193 }
194 }
195
196 if( !foundTable )
197 return nullptr;
198
199 return loadSymbolFromRow( aAliasName, *foundTable, result ).release();
200}
201
202
203void SCH_IO_DATABASE::GetSubLibraryNames( std::vector<wxString>& aNames )
204{
205 ensureSettings( wxEmptyString );
206
207 aNames.clear();
208
209 std::set<wxString> tableNames;
210
211 for( const DATABASE_LIB_TABLE& tableIter : m_settings->m_Tables )
212 {
213 if( tableNames.count( tableIter.name ) )
214 continue;
215
216 aNames.emplace_back( tableIter.name );
217 tableNames.insert( tableIter.name );
218 }
219}
220
221
222void SCH_IO_DATABASE::GetAvailableSymbolFields( std::vector<wxString>& aNames )
223{
224 std::lock_guard<std::mutex> lock( m_symbolLoadMutex );
225
226 std::copy( m_customFields.begin(), m_customFields.end(), std::back_inserter( aNames ) );
227}
228
229
230void SCH_IO_DATABASE::GetDefaultSymbolFields( std::vector<wxString>& aNames )
231{
232 std::lock_guard<std::mutex> lock( m_symbolLoadMutex );
233
234 std::copy( m_defaultShownFields.begin(), m_defaultShownFields.end(), std::back_inserter( aNames ) );
235}
236
237
238bool SCH_IO_DATABASE::TestConnection( wxString* aErrorMsg )
239{
240 if( m_conn && m_conn->IsConnected() )
241 return true;
242
243 connect();
244
245 if( aErrorMsg && ( !m_conn || !m_conn->IsConnected() ) )
246 *aErrorMsg = m_lastError;
247
248 return m_conn && m_conn->IsConnected();
249}
250
251
252size_t SCH_IO_DATABASE::computeSignature( const TABLE_RESULT_LIST& aTableResults ) const
253{
254 size_t signature = 0;
255
256 for( const auto& [table, results] : aTableResults )
257 {
258 for( const DATABASE_CONNECTION::ROW& result : results )
259 {
260 size_t rowSignature = 0;
261
262 for( const auto& [column, value] : result )
263 {
264 hash_combine( signature, std::string_view( column ) );
265
266 if( const std::string* str = std::any_cast<std::string>( &value ) )
267 hash_combine( rowSignature, std::string_view( *str ) );
268 }
269
270 hash_combine( signature, rowSignature );
271
272 if( auto it = result.find( table->symbols_col ); it != result.end() )
273 {
274 if( const std::string* str = std::any_cast<std::string>( &it->second ) )
275 {
276 LIB_ID symbolId;
277 symbolId.Parse( *str );
278
279 if( symbolId.IsValid() )
280 {
281 const UTF8& nickname = symbolId.GetLibNickname();
282 hash_combine( signature, std::string_view( nickname.c_str() ) );
283
284 if( std::optional<int> libHash = m_adapter->GetLibraryModifyHash( nickname ) )
285 hash_combine( signature, *libHash );
286 }
287 }
288 }
289 }
290 }
291
292 return signature;
293}
294
295
297 std::map<wxString, std::unique_ptr<LIB_SYMBOL>>& aSymbolCache,
298 std::map<wxString, std::pair<std::string, std::string>>& aSanitizedNameMap )
299{
300 for( const auto& [table, results] : aTableResults )
301 {
302 for( const DATABASE_CONNECTION::ROW& result : results )
303 {
304 if( !result.contains( table->key_col ) )
305 continue;
306
307 std::string rawName = std::any_cast<std::string>( result.at( table->key_col ) );
308 UTF8 sanitizedName = LIB_ID::FixIllegalChars( rawName, false );
309 std::string sanitizedKey = sanitizedName.c_str();
310 std::string prefix = ( m_settings->m_GloballyUniqueKeys || table->name.empty() )
311 ? ""
312 : fmt::format( "{}/", table->name );
313 std::string sanitizedDisplayName = fmt::format( "{}{}", prefix, sanitizedKey );
314 wxString name( sanitizedDisplayName );
315
316 aSanitizedNameMap[name] = std::make_pair( table->name, rawName );
317
318 std::unique_ptr<LIB_SYMBOL> symbol = loadSymbolFromRow( name, *table, result );
319
320 if( symbol )
321 aSymbolCache[symbol->GetName()] = std::move( symbol );
322 }
323 }
324
325 return !aSymbolCache.empty();
326}
327
328
330{
331 // Guard against re-entrant cacheLib() calls. A self-referential symbol row (issue #24249)
332 // causes m_adapter->LoadSymbol to route back into SCH_IO_DATABASE::LoadSymbol, which would
333 // otherwise call cacheLib() again while it is in the middle of populating its caches.
334 if( m_inCacheLib )
335 return;
336
337 long long currentTimestampSeconds = wxDateTime::Now().GetValue().GetValue() / 1000;
338
339 // After the initial load, the background refresh thread handles all cache updates.
340 {
341 std::shared_lock lock( m_cacheMutex );
342
343 if( m_cachePopulated )
344 return;
345 }
346
347 m_inCacheLib = true;
348
349 struct CACHE_LIB_GUARD
350 {
351 bool* flag;
352 ~CACHE_LIB_GUARD() { *flag = false; }
353 } cacheLibGuard{ &m_inCacheLib };
354
355 // Re-query the database (the connection layer caches results subject to its own max_age) and
356 // compute a lightweight signature of the raw rows so we can skip the costly materialization
357 // when nothing relevant has changed.
358 TABLE_RESULT_LIST tableResults;
359
360 for( const DATABASE_LIB_TABLE& table : m_settings->m_Tables )
361 {
362 std::vector<DATABASE_CONNECTION::ROW> results;
363
364 if( !m_conn->SelectAll( table.table, table.key_col, results ) )
365 {
366 if( !m_conn->GetLastError().empty() )
367 THROW_IO_ERRORF( _( "Error reading database table %s: %s" ), table.table, m_conn->GetLastError() );
368
369 continue;
370 }
371
372 tableResults.emplace_back( &table, std::move( results ) );
373 }
374
375 size_t signature = computeSignature( tableResults );
376
377 {
378 std::unique_lock lock( m_cacheMutex );
379
380 if( m_cachePopulated && signature == m_cacheSignature )
381 {
382 m_cacheTimestamp = currentTimestampSeconds;
383 return;
384 }
385 }
386
387 std::map<wxString, std::unique_ptr<LIB_SYMBOL>> newSymbolCache;
388 std::map<wxString, std::pair<std::string, std::string>> newSanitizedNameMap;
389
390 materializeCache( tableResults, newSymbolCache, newSanitizedNameMap );
391
392 {
393 std::unique_lock lock( m_cacheMutex );
394
395 m_nameToSymbolcache = std::move( newSymbolCache );
396 m_sanitizedNameMap = std::move( newSanitizedNameMap );
397
398 m_cacheTimestamp = currentTimestampSeconds;
399 m_cacheSignature = signature;
400 m_cachePopulated = true;
401 m_modifyHash++;
402 }
403
404 if( !m_refreshRunning.load() )
406}
407
408
410{
411 if( m_refreshRunning.exchange( true ) )
412 return;
413
414 wxLogTrace( traceDatabase, wxT( "Starting background refresh thread" ) );
416}
417
418
420{
421 m_refreshRunning = false;
422 m_refreshCV.notify_all();
423
424 if( m_refreshThread.joinable() )
425 m_refreshThread.join();
426}
427
428
430{
431 BS::this_thread::set_os_thread_name( "dblib bg" );
432
433 while( m_refreshRunning.load() )
434 {
435 long long maxAge = 0;
436
437 {
438 std::unique_lock lock( m_cacheMutex );
439
440 if( m_settings )
441 maxAge = m_settings->m_Cache.max_age;
442 }
443
444 if( maxAge <= 0 )
445 maxAge = 1;
446
447 std::shared_lock connGuard( m_cacheMutex );
448
449 if( m_conn && m_cachePopulated.load() )
450 {
451 wxLogTrace( traceDatabase, wxT( "Initiating background refresh" ) );
452
453 try
454 {
455 std::vector<std::pair<const DATABASE_LIB_TABLE*, std::vector<DATABASE_CONNECTION::ROW>>> tableResults;
456 bool querySuccess = true;
457
458 for( const DATABASE_LIB_TABLE& table : m_settings->m_Tables )
459 {
460 m_conn->ClearCache( table.table );
461
462 std::vector<DATABASE_CONNECTION::ROW> results;
463
464 if( !m_conn->SelectAll( table.table, table.key_col, results ) )
465 {
466 wxLogTrace( traceDatabase, wxT( "Background refresh: SelectAll failed for table %s" ),
467 table.table );
468 querySuccess = false;
469 break;
470 }
471
472 tableResults.emplace_back( &table, std::move( results ) );
473 }
474
475 if( querySuccess )
476 {
477 size_t signature = computeSignature( tableResults );
478
479 bool dataChanged = false;
480
481 {
482 // Upgrade to unique lock for cache mutation
483 connGuard.unlock();
484 std::unique_lock lock( m_cacheMutex );
485
486 if( signature == m_cacheSignature )
487 m_cacheTimestamp = wxDateTime::Now().GetValue().GetValue() / 1000;
488 else
489 dataChanged = true;
490 }
491
492 if( dataChanged )
493 {
494 std::map<wxString, std::unique_ptr<LIB_SYMBOL>> newSymbolCache;
495 std::map<wxString, std::pair<std::string, std::string>> newSanitizedNameMap;
496
497 materializeCache( tableResults, newSymbolCache, newSanitizedNameMap );
498
499 wxLogTrace( traceDatabase, wxT( "Background refresh: new data" ) );
500
501 {
502 std::unique_lock lock( m_cacheMutex );
503
504 m_nameToSymbolcache = std::move( newSymbolCache );
505 m_sanitizedNameMap = std::move( newSanitizedNameMap );
506
507 m_cacheTimestamp = wxDateTime::Now().GetValue().GetValue() / 1000;
508 m_cacheSignature = signature;
509 m_cachePopulated = true;
510 m_modifyHash++;
511 }
512 }
513 else
514 {
515 wxLogTrace( traceDatabase, wxT( "Background refresh: no new data" ) );
516 }
517 }
518 }
519 catch( const IO_ERROR& e )
520 {
521 wxLogTrace( traceDatabase, wxT( "Background refresh failed: %s; cache preserved" ), e.What() );
522 }
523 catch( const std::exception& e )
524 {
525 wxLogTrace( traceDatabase, wxT( "Background refresh failed: %s; cache preserved" ), e.what() );
526 }
527 }
528
529 {
530 std::unique_lock lock( m_refreshMutex );
531 m_refreshCV.wait_for( lock, std::chrono::seconds( maxAge ),
532 [this]()
533 {
534 return !m_refreshRunning.load();
535 } );
536 }
537 }
538}
539
540
541void SCH_IO_DATABASE::ensureSettings( const wxString& aSettingsPath )
542{
543 auto tryLoad =
544 [&]()
545 {
546 if( !m_settings->LoadFromFile() )
547 {
548 THROW_IO_ERRORF( _( "Could not load database library: settings file %s missing or invalid" ),
549 aSettingsPath );
550 }
551 };
552
553 if( !m_settings && !aSettingsPath.IsEmpty() )
554 {
555 std::string path( aSettingsPath.ToUTF8() );
556 m_settings = std::make_unique<DATABASE_LIB_SETTINGS>( path );
557 m_settings->SetReadOnly( true );
558
559 tryLoad();
560 }
561 else if( !m_conn && m_settings )
562 {
563 // If we have valid settings but no connection yet; reload settings in case user is editing
564 tryLoad();
565 }
566 else if( m_conn && m_settings && !aSettingsPath.IsEmpty() )
567 {
568 wxASSERT_MSG( aSettingsPath == m_settings->GetFilename(),
569 "Path changed for database library without re-initializing plugin!" );
570 }
571 else if( !m_settings )
572 {
573 wxLogTrace( traceDatabase, wxT( "ensureSettings: no settings but no valid path!" ) );
574 }
575}
576
577
579{
580 wxCHECK_RET( m_settings, "Call ensureSettings before ensureConnection!" );
581
582 connect();
583
584 if( !m_conn || !m_conn->IsConnected() )
585 {
586 THROW_IO_ERRORF( _( "Could not load database library: could not connect to database %s (%s)" ),
587 m_settings->m_Source.dsn, m_lastError );
588 }
589}
590
591
593{
594 wxCHECK_RET( m_settings, "Call ensureSettings before connect()!" );
595
596 {
597 std::unique_lock connLock( m_cacheMutex );
598
599 if( m_conn && !m_conn->IsConnected() )
600 m_conn.reset();
601 }
602
603 if( !m_conn )
604 {
605 std::unique_ptr<DATABASE_CONNECTION> newConn;
606
607 if( m_settings->m_Source.connection_string.empty() )
608 {
609 newConn = std::make_unique<DATABASE_CONNECTION>( m_settings->m_Source.dsn,
610 m_settings->m_Source.username,
611 m_settings->m_Source.password,
612 m_settings->m_Source.timeout );
613 }
614 else
615 {
616 std::string cs = m_settings->m_Source.connection_string;
617 std::string basePath( wxFileName( m_settings->GetFilename() ).GetPath().ToUTF8() );
618
619 // Database drivers that use files operate on absolute paths, so provide a mechanism
620 // for specifying on-disk databases that live next to the kicad_dbl file
621 boost::replace_all( cs, "${CWD}", basePath );
622
623 newConn = std::make_unique<DATABASE_CONNECTION>( cs, m_settings->m_Source.timeout );
624 }
625
626 if( !newConn->IsConnected() )
627 {
628 m_lastError = newConn->GetLastError();
629 return;
630 }
631
632 for( const DATABASE_LIB_TABLE& tableIter : m_settings->m_Tables )
633 {
634 // Trusted even if unreported by the driver, to support generated columns (#16952)
635 std::set<std::string> requiredColumns{ tableIter.key_col,
636 tableIter.footprints_col,
637 tableIter.symbols_col };
638
639 for( const DATABASE_FIELD_MAPPING& field : tableIter.fields )
640 requiredColumns.insert( field.column );
641
642 if( !tableIter.pins_col.empty() )
643 requiredColumns.insert( tableIter.pins_col );
644
645 // Only used if confirmed present, so a misconfigured mapping can't break the table (#23532)
646 std::set<std::string> optionalColumns{ tableIter.properties.description,
648 tableIter.properties.keywords,
651 tableIter.properties.exclude_from_board };
652
653 newConn->CacheTableInfo( tableIter.table, requiredColumns, optionalColumns );
654 }
655
656 newConn->SetCacheParams( m_settings->m_Cache.max_size, m_settings->m_Cache.max_age );
657
658 std::unique_lock connLock( m_cacheMutex );
659 m_conn = std::move( newConn );
660 }
661}
662
663
664std::optional<bool> SCH_IO_DATABASE::boolFromAny( const std::any& aVal )
665{
666 try
667 {
668 bool val = std::any_cast<bool>( aVal );
669 return val;
670 }
671 catch( const std::bad_any_cast& )
672 {
673 }
674
675 try
676 {
677 int val = std::any_cast<int>( aVal );
678 return static_cast<bool>( val );
679 }
680 catch( const std::bad_any_cast& )
681 {
682 }
683
684 try
685 {
686 wxString strval( std::any_cast<std::string>( aVal ).c_str(), wxConvUTF8 );
687
688 if( strval.IsEmpty() )
689 return std::nullopt;
690
691 strval.MakeLower();
692
693 for( const auto& trueVal : { wxS( "true" ), wxS( "yes" ), wxS( "y" ), wxS( "1" ) } )
694 {
695 if( strval.Matches( trueVal ) )
696 return true;
697 }
698
699 for( const auto& falseVal : { wxS( "false" ), wxS( "no" ), wxS( "n" ), wxS( "0" ) } )
700 {
701 if( strval.Matches( falseVal ) )
702 return false;
703 }
704 }
705 catch( const std::bad_any_cast& )
706 {
707 }
708
709 return std::nullopt;
710}
711
712
713std::unique_ptr<LIB_SYMBOL> SCH_IO_DATABASE::loadSymbolFromRow( const wxString& aSymbolName,
714 const DATABASE_LIB_TABLE& aTable,
715 const DATABASE_CONNECTION::ROW& aRow )
716{
717 std::unique_ptr<LIB_SYMBOL> symbol = nullptr;
718
719 if( aRow.contains( aTable.symbols_col ) )
720 {
721 std::string symbols = std::any_cast<std::string>( aRow.at( aTable.symbols_col ) );
722 wxString symbolsStr = wxString( symbols.c_str(), wxConvUTF8 );
723 wxStringTokenizer tokenizer( symbolsStr, ";\t\r\n", wxTOKEN_STRTOK );
724
725 std::vector<LIB_ID> symbolIds;
726
727 while( tokenizer.HasMoreTokens() )
728 {
729 wxString token = tokenizer.GetNextToken();
730 LIB_ID id;
731 id.Parse( std::string( token.ToUTF8() ) );
732
733 if( id.IsValid() )
734 symbolIds.push_back( id );
735 }
736
737 // A row's Symbols column may resolve back into the same database library (issue #24249,
738 // e.g. a mistyped library nickname). The adapter would route that lookup back into
739 // SCH_IO_DATABASE::LoadSymbol and re-enter loadSymbolFromRow on the same row until the
740 // stack overflows. Track in-flight LIB_IDs and skip the recursive load on re-entry.
741 std::vector<LIB_SYMBOL*> sourceSymbols;
742 std::vector<wxString> sourceSymbolNames;
743
744 for( const LIB_ID& symbolId : symbolIds )
745 {
746 wxString symbolIdStr = symbolId.Format().wx_str();
747
748 if( !m_inProgressLoads.insert( symbolIdStr ).second )
749 {
750 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: cycle detected resolving '%s' "
751 "(row '%s' in table '%s'); skipping recursive load" ),
752 symbolIdStr, aSymbolName, aTable.name );
753 continue;
754 }
755
756 struct CYCLE_GUARD
757 {
758 std::unordered_set<wxString>* set;
759 wxString key;
760 ~CYCLE_GUARD() { set->erase( key ); }
761 } guard{ &m_inProgressLoads, symbolIdStr };
762
763 LIB_SYMBOL* src = nullptr;
764
765 {
766 std::lock_guard lock( m_symbolLoadMutex );
767 src = m_adapter->LoadSymbol( symbolId );
768 }
769
770 if( src )
771 {
772 sourceSymbols.push_back( src );
773 sourceSymbolNames.push_back( src->GetName() );
774 }
775 else
776 {
777 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: source symbol '%s' not found" ), symbolIdStr );
778 }
779 }
780
781 if( sourceSymbols.empty() )
782 {
783 // Actual symbol not found: return metadata only; error will be indicated in the
784 // symbol chooser
785 symbol.reset( new LIB_SYMBOL( aSymbolName ) );
786 }
787 else if( sourceSymbols.size() == 1 )
788 {
789 symbol.reset( sourceSymbols[0]->Duplicate() );
790 symbol->SetSourceLibId( symbolIds[0] );
791 symbol->SetName( aSymbolName );
792 }
793 else
794 {
795 // If the database row specifies multiple symbols, put together a composite,
796 // but since source symbols may already have multiple body styles, we
797 // need to flatten out any common to all body styles items on source symbols.
798 // Since we don't support varying all properties, take those from the first one.
799
800 symbol.reset( new LIB_SYMBOL( aSymbolName ) );
801 symbol->SetSourceLibId( symbolIds[0] );
802
803 symbol->SetUnitCount( sourceSymbols[0]->GetUnitCount(), false );
804 symbol->SetPowerSymbolProp( sourceSymbols[0]->IsPower() );
805 symbol->SetShowPinNames( sourceSymbols[0]->GetShowPinNames() );
806 symbol->SetShowPinNumbers( sourceSymbols[0]->GetShowPinNumbers() );
807 symbol->SetPinNameOffset( sourceSymbols[0]->GetPinNameOffset() );
808
809 for( FIELD_T fieldId : MANDATORY_FIELDS )
810 {
811 if( SCH_FIELD* srcField = sourceSymbols[0]->GetField( fieldId ) )
812 {
813 SCH_FIELD* dstField = symbol->GetField( fieldId );
814 *dstField = *srcField;
815 dstField->SetParent( symbol.get() );
816 }
817 }
818
819 std::vector<wxString> bodyStyleNames;
820 std::vector<int> sourceBodyStyleCounts;
821 std::vector<int> compositeBodyStyleBase;
822
823 int nextBodyStyle = 1;
824
825 for( size_t i = 0; i < sourceSymbols.size(); ++i )
826 {
827 int srcCount = std::max( 1, sourceSymbols[i]->GetBodyStyleCount() );
828 sourceBodyStyleCounts.push_back( srcCount );
829 compositeBodyStyleBase.push_back( nextBodyStyle );
830
831 if( srcCount == 1 )
832 {
833 bodyStyleNames.push_back( sourceSymbolNames[i] );
834 }
835 else
836 {
837 for( int style = 1; style <= srcCount; ++style )
838 {
839 wxString styleName = sourceSymbols[i]->GetBodyStyleDescription( style, false );
840 bodyStyleNames.push_back( sourceSymbolNames[i] + wxT( " (" ) + styleName + wxT( ")" ) );
841 }
842 }
843
844 nextBodyStyle += srcCount;
845 }
846
847 int totalBodyStyles = nextBodyStyle - 1;
848
849 symbol->SetHasDeMorganBodyStyles( false );
850 symbol->SetBodyStyleNames( bodyStyleNames );
851
852 std::set<wxString> mergedFieldNames;
853
854 for( size_t i = 0; i < sourceSymbols.size(); ++i )
855 {
856 std::unique_ptr<LIB_SYMBOL> srcSymbol = sourceSymbols[i]->Flatten();
857 int srcCount = sourceBodyStyleCounts[i];
858 int base = compositeBodyStyleBase[i];
859
860 for( SCH_ITEM& item : srcSymbol->GetDrawItems() )
861 {
862 if( item.Type() == SCH_FIELD_T )
863 {
864 SCH_FIELD& field = static_cast<SCH_FIELD&>( item );
865
866 if( field.IsMandatory() || !mergedFieldNames.insert( field.GetName() ).second )
867 continue;
868
869 int targetStyle;
870
871 if( item.GetBodyStyle() == 0 )
872 targetStyle = base;
873 else
874 targetStyle = base + ( item.GetBodyStyle() - 1 );
875
876 if( targetStyle > totalBodyStyles )
877 continue;
878
879 SCH_ITEM* newItem = item.Duplicate( IGNORE_PARENT_GROUP );
880 newItem->SetParent( symbol.get() );
881 newItem->SetBodyStyle( targetStyle );
882 symbol->AddDrawItem( newItem, false );
883 }
884 else if( item.GetBodyStyle() == 0 )
885 {
886 for( int bodyStyle = 0; bodyStyle < srcCount; ++bodyStyle )
887 {
888 SCH_ITEM* newItem = item.Duplicate( IGNORE_PARENT_GROUP );
889 newItem->SetParent( symbol.get() );
890 newItem->SetBodyStyle( base + bodyStyle );
891 symbol->AddDrawItem( newItem, false );
892 }
893 }
894 else
895 {
896 int targetStyle = base + ( item.GetBodyStyle() - 1 );
897
898 if( targetStyle > totalBodyStyles )
899 continue;
900
901 SCH_ITEM* newItem = item.Duplicate( IGNORE_PARENT_GROUP );
902 newItem->SetParent( symbol.get() );
903 newItem->SetBodyStyle( targetStyle );
904 symbol->AddDrawItem( newItem, false );
905 }
906 }
907 }
908
909 symbol->GetDrawItems().sort();
910 }
911 }
912 else
913 {
914 symbol.reset( new LIB_SYMBOL( aSymbolName ) );
915 }
916
917 LIB_ID libId = symbol->GetLibId();
918 libId.SetSubLibraryName( aTable.name );
919 symbol->SetLibId( libId );
920
921 wxArrayString footprintsList;
922
923 if( aRow.count( aTable.footprints_col ) )
924 {
925 std::string footprints = std::any_cast<std::string>( aRow.at( aTable.footprints_col ) );
926
927 wxString footprintsStr = wxString( footprints.c_str(), wxConvUTF8 );
928 wxStringTokenizer tokenizer( footprintsStr, ";\t\r\n", wxTOKEN_STRTOK );
929
930 while( tokenizer.HasMoreTokens() )
931 footprintsList.Add( tokenizer.GetNextToken() );
932
933 if( footprintsList.size() > 0 )
934 symbol->GetFootprintField().SetText( footprintsList[0] );
935 }
936 else
937 {
938 wxLogTrace( traceDatabase, wxT( "loadSymboFromRow: footprint field %s not found." ), aTable.footprints_col );
939 }
940
941 // Pin-to-pad maps (issue #2282): attach non-destructively. The pins column carries either the
942 // spec-form named object { "pin_maps": [...], "associated_footprints": [...] } or the legacy
943 // flat MR !2540 array (read for one release, bound to the row's footprints).
944 if( !aTable.pins_col.empty() && aRow.count( aTable.pins_col ) )
945 {
946 try
947 {
948 std::string jsonStr = std::any_cast<std::string>( aRow.at( aTable.pins_col ) );
949
950 if( !jsonStr.empty() )
951 {
952 nlohmann::json json = nlohmann::json::parse( jsonStr );
953
954 if( json.is_object() && json.contains( "pin_maps" ) )
955 {
956 symbol->SetPinMaps( ParsePinMapSet( json ) );
957 symbol->SetAssociatedFootprints( ParseAssociatedFootprints( json ) );
958 }
959 else if( !footprintsList.IsEmpty() )
960 {
961 std::unordered_map<wxString, std::vector<wxString>> assignments = ParseLegacyPinAssignments( json );
962
963 if( !assignments.empty() )
964 {
965 const wxString mapName = wxS( "Database" );
966
967 symbol->PinMaps().AddOrReplace( MakeLegacyPinMap( mapName, assignments ) );
968
969 // Bind the one named map to every footprint the row offers, mirroring the
970 // old behaviour where the assignment applied regardless of footprint.
971 std::vector<ASSOCIATED_FOOTPRINT> associations;
972
973 for( const wxString& footprint : footprintsList )
974 {
975 LIB_ID fpId;
976 fpId.Parse( footprint );
977 associations.push_back( { fpId, mapName } );
978 }
979
980 symbol->SetAssociatedFootprints( std::move( associations ) );
981 }
982 }
983 }
984 }
985 catch( const std::exception& e )
986 {
987 // Surface a malformed pin-map payload to the user instead of silently dropping it; the
988 // symbol still loads without the map (issue #2282).
989 Report( wxString::Format( _( "Error parsing pin map for database symbol '%s': %s" ),
990 aSymbolName, e.what() ),
992 }
993 }
994
995 if( !aTable.properties.description.empty() && aRow.count( aTable.properties.description ) )
996 {
997 wxString value(
998 std::any_cast<std::string>( aRow.at( aTable.properties.description ) ).c_str(),
999 wxConvUTF8 );
1000 symbol->SetDescription( value );
1001 }
1002
1003 if( !aTable.properties.keywords.empty() && aRow.count( aTable.properties.keywords ) )
1004 {
1005 wxString value( std::any_cast<std::string>( aRow.at( aTable.properties.keywords ) ).c_str(),
1006 wxConvUTF8 );
1007 symbol->SetKeyWords( value );
1008 }
1009
1010 if( !aTable.properties.footprint_filters.empty()
1011 && aRow.count( aTable.properties.footprint_filters ) )
1012 {
1013 wxString value( std::any_cast<std::string>( aRow.at( aTable.properties.footprint_filters ) )
1014 .c_str(),
1015 wxConvUTF8 );
1016 footprintsList.push_back( value );
1017 }
1018
1019 symbol->SetFPFilters( footprintsList );
1020
1021 if( !aTable.properties.exclude_from_sim.empty()
1022 && aRow.count( aTable.properties.exclude_from_sim ) )
1023 {
1024 std::optional<bool> val = boolFromAny( aRow.at( aTable.properties.exclude_from_sim ) );
1025
1026 if( val )
1027 {
1028 symbol->SetExcludedFromSim( *val );
1029 }
1030 else
1031 {
1032 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: exclude_from_sim value for %s "
1033 "could not be cast to a boolean" ), aSymbolName );
1034 }
1035 }
1036
1037 if( !aTable.properties.exclude_from_board.empty()
1038 && aRow.count( aTable.properties.exclude_from_board ) )
1039 {
1040 std::optional<bool> val = boolFromAny( aRow.at( aTable.properties.exclude_from_board ) );
1041
1042 if( val )
1043 {
1044 symbol->SetExcludedFromBoard( *val );
1045 }
1046 else
1047 {
1048 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: exclude_from_board value for %s "
1049 "could not be cast to a boolean" ), aSymbolName );
1050 }
1051 }
1052
1053 if( !aTable.properties.exclude_from_bom.empty()
1054 && aRow.count( aTable.properties.exclude_from_bom ) )
1055 {
1056 std::optional<bool> val = boolFromAny( aRow.at( aTable.properties.exclude_from_bom ) );
1057
1058 if( val )
1059 {
1060 symbol->SetExcludedFromBOM( *val );
1061 }
1062 else
1063 {
1064 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: exclude_from_bom value for %s "
1065 "could not be cast to a boolean" ), aSymbolName );
1066 }
1067 }
1068
1069 std::vector<SCH_FIELD*> fields;
1070 symbol->GetFields( fields );
1071
1072 std::unordered_map<wxString, SCH_FIELD*> fieldsMap;
1073
1074 for( SCH_FIELD* field : fields )
1075 fieldsMap[field->GetName()] = field;
1076
1077 static const wxString c_valueFieldName( wxS( "Value" ) );
1078 static const wxString c_datasheetFieldName( wxS( "Datasheet" ) );
1079 static const wxString c_footprintFieldName( wxS( "Footprint" ) );
1080
1081 // User fields produced from the database mapping must appear in the order they are
1082 // declared in the .kicad_dbl file, not in lexicographic order of their values. Start
1083 // assigning ordinals above whatever the source LIB_SYMBOL already uses so that any
1084 // pre-existing user fields keep their relative position before the database fields.
1085 int dbFieldOrdinal = symbol->GetNextFieldOrdinal();
1086
1087 for( const DATABASE_FIELD_MAPPING& mapping : aTable.fields )
1088 {
1089 if( !aRow.count( mapping.column ) )
1090 {
1091 wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: field %s not found in result" ), mapping.column );
1092 continue;
1093 }
1094
1095 // Skip footprint field if it maps to the footprints column, since that column is
1096 // already processed above with tokenization for semicolon-separated multiple footprints.
1097 if( mapping.name_wx == c_footprintFieldName && mapping.column == aTable.footprints_col )
1098 continue;
1099
1100 std::string strValue;
1101
1102 try
1103 {
1104 strValue = std::any_cast<std::string>( aRow.at( mapping.column ) );
1105 }
1106 catch( std::bad_any_cast& )
1107 {
1108 }
1109
1110 wxString value( strValue.c_str(), wxConvUTF8 );
1111
1112 if( mapping.name_wx == c_valueFieldName )
1113 {
1114 SCH_FIELD& field = symbol->GetValueField();
1115 field.SetText( value );
1116
1117 if( !mapping.inherit_properties )
1118 {
1119 field.SetVisible( mapping.visible_on_add );
1120 field.SetNameShown( mapping.show_name );
1121 }
1122
1123 continue;
1124 }
1125 else if( mapping.name_wx == c_datasheetFieldName )
1126 {
1127 SCH_FIELD& field = symbol->GetDatasheetField();
1128 field.SetText( value );
1129
1130 if( !mapping.inherit_properties )
1131 {
1132 field.SetVisible( mapping.visible_on_add );
1133 field.SetNameShown( mapping.show_name );
1134
1135 if( mapping.visible_on_add )
1136 field.SetAutoAdded( true );
1137 }
1138
1139 continue;
1140 }
1141
1142 SCH_FIELD* field;
1143 bool isNew = false;
1144
1145 if( fieldsMap.count( mapping.name_wx ) )
1146 {
1147 field = fieldsMap[mapping.name_wx];
1148 }
1149 else
1150 {
1151 field = new SCH_FIELD( nullptr, FIELD_T::USER );
1152 field->SetName( mapping.name_wx );
1153 isNew = true;
1154 fieldsMap[mapping.name_wx] = field;
1155 }
1156
1157 // Assign a sort-order ordinal so the property editor and BOM see the fields in the
1158 // order declared in the .kicad_dbl file. Without this, all USER fields share the
1159 // same FIELD_T::USER id and fall through to value-based comparison in operator<.
1160 // NB: a DB mapping that lands on a mandatory field by name (e.g. Reference or
1161 // Description) must keep its FIELD_T identity for downstream lookups.
1162 field->SetOrdinal( dbFieldOrdinal++, field->IsMandatory() ? field->GetId() : FIELD_T::USER );
1163
1164 if( !mapping.inherit_properties || isNew )
1165 {
1166 field->SetVisible( mapping.visible_on_add );
1167 field->SetAutoAdded( true );
1168 field->SetNameShown( mapping.show_name );
1169 }
1170
1171 field->SetText( value );
1172
1173 if( isNew )
1174 symbol->AddDrawItem( field, false );
1175
1176 m_customFields.insert( mapping.name_wx );
1177
1178 if( mapping.visible_in_chooser )
1179 m_defaultShownFields.insert( mapping.name_wx );
1180 }
1181
1182 symbol->GetDrawItems().sort();
1183
1184 // Field mappings (including Description) are applied with SCH_FIELD::SetText, which does not
1185 // refresh the cached values the library tree and chooser read. Without this the upper chooser
1186 // panel keeps the source symbol's description while the details panel shows the database value.
1187 symbol->RefreshLibraryTreeCaches();
1188
1189 return symbol;
1190}
1191
1192
1194{
1195 return new DIALOG_DATABASE_LIB_SETTINGS( aParent, this );
1196}
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:80
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:153
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:342
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
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
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:119
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:133
wxString GetName() const override
Definition lib_symbol.h:181
bool IsMandatory() const
void SetOrdinal(int aOrdinal, FIELD_T aType)
Definition sch_field.h:148
FIELD_T GetId() const
Definition sch_field.h:142
void SetAutoAdded(bool aAutoAdded)
Definition sch_field.h:247
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:229
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::shared_mutex m_cacheMutex
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)
std::atomic< 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...
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
DIALOG_SHIM * CreateConfigurationDialog(wxWindow *aParent) override
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 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 * 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
SCH_IO(const wxString &aName)
Definition sch_io.h:407
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:165
SCH_ITEM * Duplicate(bool addToParentGroup, SCH_COMMIT *aCommit=nullptr, bool doClone=false) const
Routine to create a new copy of given item.
Definition sch_item.cpp:170
virtual void SetBodyStyle(int aBodyStyle)
Definition sch_item.h:246
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
const char *const traceDatabase
#define _(s)
#define IGNORE_PARENT_GROUP
Definition eda_item.h:55
nlohmann::json json
Definition gerbview.cpp:50
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
#define THROW_IO_ERRORF(msg,...)
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
@ 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 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