From ae6a2a6443a3855fb9fe58320a27b979812d51f0 Mon Sep 17 00:00:00 2001 From: Jon Evans Date: Sun, 29 May 2022 15:29:32 -0400 Subject: [PATCH] ADDED: Database libraries MVP Allows placing parts from an external database that reference symbols from another loaded library. Includes: - nanodbc wrapper - database schematic library plugin - basic tests Fixes https://gitlab.com/kicad/code/kicad/-/issues/7436 --- cmake/KiCadQABuildUtils.cmake | 6 + common/CMakeLists.txt | 7 + common/database/database_connection.cpp | 431 ++++++++++++++++++ common/database/database_lib_settings.cpp | 105 +++++ common/lib_table_base.cpp | 3 +- common/wildcards_and_files_ext.cpp | 12 +- eeschema/CMakeLists.txt | 1 + eeschema/dialogs/panel_sym_lib_table.cpp | 7 +- eeschema/lib_field.cpp | 4 + eeschema/lib_field.h | 4 + eeschema/sch_io_mgr.cpp | 5 + eeschema/sch_io_mgr.h | 8 + .../database/sch_database_plugin.cpp | 323 +++++++++++++ .../database/sch_database_plugin.h | 106 +++++ eeschema/sch_symbol.cpp | 3 +- eeschema/symbol_lib_table.cpp | 8 +- eeschema/tools/sch_drawing_tools.cpp | 3 + eeschema/widgets/symbol_preview_widget.cpp | 12 + include/database/database_connection.h | 109 +++++ include/database/database_lib_settings.h | 94 ++++ include/fmt.h | 34 ++ include/lib_table_base.h | 19 +- include/wildcards_and_files_ext.h | 2 + qa/data/dblib/database.sqlite | Bin 0 -> 139264 bytes qa/data/dblib/qa_dblib.kicad_dbl | 114 +++++ qa/unittests/common/CMakeLists.txt | 10 + qa/unittests/common/test_database.cpp | 69 +++ thirdparty/nanodbc/CMakeLists.txt | 23 +- 28 files changed, 1504 insertions(+), 18 deletions(-) create mode 100644 common/database/database_connection.cpp create mode 100644 common/database/database_lib_settings.cpp create mode 100644 eeschema/sch_plugins/database/sch_database_plugin.cpp create mode 100644 eeschema/sch_plugins/database/sch_database_plugin.h create mode 100644 include/database/database_connection.h create mode 100644 include/database/database_lib_settings.h create mode 100644 include/fmt.h create mode 100644 qa/data/dblib/database.sqlite create mode 100644 qa/data/dblib/qa_dblib.kicad_dbl create mode 100644 qa/unittests/common/test_database.cpp diff --git a/cmake/KiCadQABuildUtils.cmake b/cmake/KiCadQABuildUtils.cmake index 91f245d023..28272a8e08 100644 --- a/cmake/KiCadQABuildUtils.cmake +++ b/cmake/KiCadQABuildUtils.cmake @@ -30,8 +30,14 @@ option( KICAD_TEST_XML_OUTPUT "Cause unit tests to output xUnit results where possible for more granular CI reporting." OFF ) + mark_as_advanced( KICAD_TEST_XML_OUTPUT ) # Only CI tools need this +option( KICAD_TEST_DATABASE_LIBRARIES + "Enable the database libraries QA tests (requires SQlite3 ODBC driver to be installed" + OFF +) + # This is a "meta" target that is used to collect all tests add_custom_target( qa_all_tests ) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index b425e99c5d..67d7f80ee5 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -443,6 +443,9 @@ set( COMMON_SRCS project/project_archiver.cpp project/project_file.cpp project/project_local_settings.cpp + + database/database_connection.cpp + database/database_lib_settings.cpp ) add_library( common STATIC @@ -492,6 +495,10 @@ target_include_directories( common PUBLIC $ ) +target_include_directories( common SYSTEM PUBLIC + $ + ) + set( PCB_COMMON_SRCS base_screen.cpp diff --git a/common/database/database_connection.cpp b/common/database/database_connection.cpp new file mode 100644 index 0000000000..51f7f81b1c --- /dev/null +++ b/common/database/database_connection.cpp @@ -0,0 +1,431 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Jon Evans + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include +#include +#include +#include // SQL_IDENTIFIER_QUOTE_CHAR +#include + +#include + + +const char* const traceDatabase = "KICAD_DATABASE"; + +/** + * When Unicode support is enabled in nanodbc, string formats are used matching the appropriate + * character set of the platform. KiCad uses UTF-8 encoded strings internally, but different + * platforms use different encodings for SQL strings. Unicode mode must be enabled for compilation + * on Windows, since Visual Studio forces the use of Unicode SQL headers if any part of the project + * has Unicode enabled. + */ + +/** + * Converts a string from KiCad-native to nanodbc-native + * @param aString is a UTF-8 encoded string + * @return a string in nanodbc's platform-specific representation + */ +nanodbc::string fromUTF8( const std::string& aString ) +{ + return boost::locale::conv::utf_to_utf( aString ); +} + + +/** + * Converts a string from nanodbc-native to KiCad-native + * @param aString is a string encoded in nanodbc's platform-specific way + * @return a string with UTF-8 encoding + */ +std::string toUTF8( const nanodbc::string& aString ) +{ + return boost::locale::conv::utf_to_utf( aString ); +} + + +DATABASE_CONNECTION::DATABASE_CONNECTION( const std::string& aDataSourceName, + const std::string& aUsername, + const std::string& aPassword, int aTimeoutSeconds, + bool aConnectNow ) : + m_quoteChar( '"' ) +{ + m_dsn = aDataSourceName; + m_user = aUsername; + m_pass = aPassword; + m_timeout = aTimeoutSeconds; + + if( aConnectNow ) + Connect(); +} + + +DATABASE_CONNECTION::DATABASE_CONNECTION( const std::string& aConnectionString, + int aTimeoutSeconds, bool aConnectNow ) : + m_quoteChar( '"' ) +{ + m_connectionString = aConnectionString; + m_timeout = aTimeoutSeconds; + + if( aConnectNow ) + Connect(); +} + + +DATABASE_CONNECTION::~DATABASE_CONNECTION() +{ + Disconnect(); + m_conn.reset(); +} + + +bool DATABASE_CONNECTION::Connect() +{ + nanodbc::string dsn = fromUTF8( m_dsn ); + nanodbc::string user = fromUTF8( m_user ); + nanodbc::string pass = fromUTF8( m_pass ); + nanodbc::string cs = fromUTF8( m_connectionString ); + + try + { + if( cs.empty() ) + { + wxLogTrace( traceDatabase, wxT( "Creating connection to DSN %s" ), m_dsn ); + m_conn = std::make_unique( dsn, user, pass, m_timeout ); + } + else + { + wxLogTrace( traceDatabase, wxT( "Creating connection with connection string" ) ); + m_conn = std::make_unique( cs, m_timeout ); + } + } + catch( nanodbc::database_error& e ) + { + m_lastError = e.what(); + return false; + } + + if( IsConnected() ) + { + syncTables(); + cacheColumns(); + getQuoteChar(); + } + + return IsConnected(); +} + + +bool DATABASE_CONNECTION::Disconnect() +{ + if( !m_conn ) + { + wxLogTrace( traceDatabase, wxT( "Note: Disconnect() called without valid connection" ) ); + return false; + } + + m_conn->disconnect(); + + return !m_conn->connected(); +} + + +bool DATABASE_CONNECTION::IsConnected() const +{ + if( !m_conn ) + return false; + + return m_conn->connected(); +} + + +bool DATABASE_CONNECTION::syncTables() +{ + if( !m_conn ) + return false; + + m_tables.clear(); + + try + { + nanodbc::catalog catalog( *m_conn ); + nanodbc::catalog::tables tables = catalog.find_tables(); + + while( tables.next() ) + { + std::string key = toUTF8( tables.table_name() ); + m_tables[key] = toUTF8( tables.table_type() ); + } + } + catch( nanodbc::database_error& e ) + { + m_lastError = e.what(); + wxLogTrace( traceDatabase, wxT( "Exception while syncing tables: %s" ), m_lastError ); + return false; + } + + return true; +} + + +bool DATABASE_CONNECTION::cacheColumns() +{ + if( !m_conn ) + return false; + + m_columnCache.clear(); + + for( const auto& tableIter : m_tables ) + { + try + { + nanodbc::catalog catalog( *m_conn ); + nanodbc::catalog::columns columns = + catalog.find_columns( NANODBC_TEXT( "" ), fromUTF8( tableIter.first ) ); + + while( columns.next() ) + { + std::string columnKey = toUTF8( columns.column_name() ); + m_columnCache[tableIter.first][columnKey] = columns.data_type(); + } + + } + catch( nanodbc::database_error& e ) + { + m_lastError = e.what(); + wxLogTrace( traceDatabase, wxT( "Exception while syncing columns for table %s: %s" ), + tableIter.first, m_lastError ); + return false; + } + } + + return true; +} + + +bool DATABASE_CONNECTION::getQuoteChar() +{ + if( !m_conn ) + return false; + + try + { + nanodbc::string qc = m_conn->get_info( SQL_IDENTIFIER_QUOTE_CHAR ); + + if( qc.empty() ) + return false; + + m_quoteChar = *toUTF8( qc ).begin(); + + wxLogTrace( traceDatabase, wxT( "Quote char retrieved: %c" ), m_quoteChar ); + } + catch( nanodbc::database_error& e ) + { + wxLogTrace( traceDatabase, wxT( "Exception while querying quote char: %s" ), m_lastError ); + return false; + } + + return true; +} + + +bool DATABASE_CONNECTION::SelectOne( const std::string& aTable, + const std::pair& aWhere, + DATABASE_CONNECTION::ROW& aResult ) +{ + if( !m_conn ) + { + wxLogTrace( traceDatabase, wxT( "Called SelectOne without valid connection!" ) ); + return false; + } + + auto tableMapIter = m_tables.find( aTable ); + + if( tableMapIter == m_tables.end() ) + { + wxLogTrace( traceDatabase, wxT( "SelectOne: requested table %s not found in cache" ), + aTable ); + return false; + } + + const std::string& tableName = tableMapIter->first; + + if( !m_columnCache.count( tableName ) ) + { + wxLogTrace( traceDatabase, wxT( "SelectOne: requested table %s missing from column cache" ), + tableName ); + return false; + } + + auto columnCacheIter = m_columnCache.at( tableName ).find( aWhere.first ); + + if( columnCacheIter == m_columnCache.at( tableName ).end() ) + { + wxLogTrace( traceDatabase, wxT( "SelectOne: requested column %s not found in cache for %s" ), + aWhere.first, tableName ); + return false; + } + + const std::string& columnName = columnCacheIter->first; + + nanodbc::statement statement( *m_conn ); + + nanodbc::string query = fromUTF8( fmt::format( "SELECT * FROM {}{}{} WHERE {}{}{} = ?", + m_quoteChar, tableName, m_quoteChar, + m_quoteChar, columnName, m_quoteChar ) ); + + try + { + statement.prepare( query ); + statement.bind( 0, aWhere.second.c_str() ); + } + catch( nanodbc::database_error& e ) + { + m_lastError = e.what(); + wxLogTrace( traceDatabase, wxT( "Exception while preparing statement for SelectOne: %s" ), + m_lastError ); + return false; + } + + wxLogTrace( traceDatabase, wxT( "SelectOne: `%s` with parameter `%s`" ), toUTF8( query ), + aWhere.second ); + + nanodbc::result results; + + try + { + results = nanodbc::execute( statement ); + } + catch( nanodbc::database_error& e ) + { + m_lastError = e.what(); + wxLogTrace( traceDatabase, wxT( "Exception while executing statement for SelectOne: %s" ), + m_lastError ); + return false; + } + + if( !results.first() ) + { + wxLogTrace( traceDatabase, wxT( "SelectOne: no results returned from query" ) ); + return false; + } + + wxLogTrace( traceDatabase, wxT( "SelectOne: %ld results returned from query" ), + results.rows() ); + + aResult.clear(); + + try + { + for( short i = 0; i < results.columns(); ++i ) + { + std::string column = toUTF8( results.column_name( i ) ); + aResult[ column ] = toUTF8( results.get( i, NANODBC_TEXT( "" ) ) ); + } + } + catch( nanodbc::database_error& e ) + { + m_lastError = e.what(); + wxLogTrace( traceDatabase, wxT( "Exception while parsing results from SelectOne: %s" ), + m_lastError ); + return false; + } + + return true; +} + + +bool DATABASE_CONNECTION::SelectAll( const std::string& aTable, std::vector& aResults ) +{ + if( !m_conn ) + { + wxLogTrace( traceDatabase, wxT( "Called SelectAll without valid connection!" ) ); + return false; + } + + auto tableMapIter = m_tables.find( aTable ); + + if( tableMapIter == m_tables.end() ) + { + wxLogTrace( traceDatabase, wxT( "SelectAll: requested table %s not found in cache" ), + aTable ); + return false; + } + + nanodbc::statement statement( *m_conn ); + + nanodbc::string query = fromUTF8( fmt::format( "SELECT * FROM {}{}{}", m_quoteChar, + tableMapIter->first, m_quoteChar ) ); + + wxLogTrace( traceDatabase, wxT( "SelectAll: `%s`" ), toUTF8( query ) ); + + try + { + statement.prepare( query ); + } + catch( nanodbc::database_error& e ) + { + m_lastError = e.what(); + wxLogTrace( traceDatabase, wxT( "Exception while preparing query for SelectAll: %s" ), + m_lastError ); + return false; + } + + nanodbc::result results; + + try + { + results = nanodbc::execute( statement ); + } + catch( nanodbc::database_error& e ) + { + m_lastError = e.what(); + wxLogTrace( traceDatabase, wxT( "Exception while executing query for SelectAll: %s" ), + m_lastError ); + return false; + } + + if( !results.first() ) + return false; + + try + { + do + { + ROW result; + + for( short j = 0; j < results.columns(); ++j ) + { + std::string column = toUTF8( results.column_name( j ) ); + result[column] = toUTF8( results.get( j, NANODBC_TEXT( "" ) ) ); + } + + aResults.emplace_back( std::move( result ) ); + + } while( results.next() ); + } + catch( nanodbc::database_error& e ) + { + m_lastError = e.what(); + wxLogTrace( traceDatabase, wxT( "Exception while parsing results from SelectAll: %s" ), + m_lastError ); + return false; + } + + return true; +} diff --git a/common/database/database_lib_settings.cpp b/common/database/database_lib_settings.cpp new file mode 100644 index 0000000000..5cc9777125 --- /dev/null +++ b/common/database/database_lib_settings.cpp @@ -0,0 +1,105 @@ +/* +* This program source code file is part of KiCad, a free EDA CAD application. +* +* Copyright (C) 2022 Jon Evans +* Copyright (C) 2022 KiCad Developers, see AUTHORS.TXT for contributors. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, you may find one here: +* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html +* or you may search the http://www.gnu.org website for the version 2 license, +* or you may write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +*/ + +#include + +#include +#include +#include + + +const int dblibSchemaVersion = 0; + + +DATABASE_LIB_SETTINGS::DATABASE_LIB_SETTINGS( const std::string& aFilename ) : + JSON_SETTINGS( aFilename, SETTINGS_LOC::NONE, dblibSchemaVersion ) +{ + + m_params.emplace_back( new PARAM( "source.dsn", &m_Source.dsn, "" ) ); + + m_params.emplace_back( new PARAM( "source.username", &m_Source.username, "" ) ); + + m_params.emplace_back( new PARAM( "source.password", &m_Source.password, "" ) ); + + m_params.emplace_back( + new PARAM( "source.connection_string", &m_Source.connection_string, "" ) ); + + m_params.emplace_back( new PARAM( "source.timeout_seconds", &m_Source.timeout, 2 ) ); + + m_params.emplace_back( new PARAM_LAMBDA( + "libraries", + [&]() -> nlohmann::json + { + // TODO: implement this; libraries are read-only from KiCad at the moment + return {}; + }, + [&]( const nlohmann::json aObj ) + { + m_Tables.clear(); + + if( !aObj.is_array() ) + return; + + for( const nlohmann::json& entry : aObj ) + { + if( entry.empty() || !entry.is_object() ) + continue; + + DATABASE_LIB_TABLE table; + + table.name = entry["name"].get(); + table.table = entry["table"].get(); + table.key_col = entry["key"].get(); + table.symbols_col = entry["symbols"].get(); + table.footprints_col = entry["footprints"].get(); + + if( entry.contains( "fields" ) && entry["fields"].is_array() ) + { + for( const nlohmann::json& fieldJson : entry["fields"] ) + { + if( fieldJson.empty() || !fieldJson.is_object() ) + continue; + + table.fields.emplace_back( + DATABASE_FIELD_MAPPING( + { + fieldJson["column"].get(), + fieldJson["name"].get(), + fieldJson["visible_on_add"].get(), + fieldJson["visible_in_chooser"].get() + } ) ); + } + } + + m_Tables.emplace_back( std::move( table ) ); + } + }, + {} ) ); +} + + +wxString DATABASE_LIB_SETTINGS::getFileExt() const +{ + return DatabaseLibraryFileExtension; +} diff --git a/common/lib_table_base.cpp b/common/lib_table_base.cpp index eb478b83d0..524c473473 100644 --- a/common/lib_table_base.cpp +++ b/common/lib_table_base.cpp @@ -184,7 +184,6 @@ LIB_TABLE_ROW* LIB_TABLE::findRow( const wxString& aNickName, bool aCheckIfEnabl do { - std::lock_guard lock( cur->m_nickIndexMutex ); cur->ensureIndex(); for( const std::pair& entry : cur->nickIndex ) @@ -301,6 +300,8 @@ bool LIB_TABLE::InsertRow( LIB_TABLE_ROW* aRow, bool doReplace ) INDEX_CITER it = nickIndex.find( aRow->GetNickName() ); + aRow->SetParent( this ); + if( it == nickIndex.end() ) { rows.push_back( aRow ); diff --git a/common/wildcards_and_files_ext.cpp b/common/wildcards_and_files_ext.cpp index 5d9e301bc0..287b47bab2 100644 --- a/common/wildcards_and_files_ext.cpp +++ b/common/wildcards_and_files_ext.cpp @@ -140,6 +140,7 @@ const std::string GerberJobFileExtension( "gbrjob" ); const std::string HtmlFileExtension( "html" ); const std::string EquFileExtension( "equ" ); const std::string HotkeyFileExtension( "hotkeys" ); +const std::string DatabaseLibraryFileExtension( "kicad_dbl" ); const std::string ArchiveFileExtension( "zip" ); @@ -206,10 +207,19 @@ wxString LegacySymbolLibFileWildcard() } +wxString DatabaseLibFileWildcard() +{ + return _( "KiCad database library files" ) + + AddFileExtListToFilter( { DatabaseLibraryFileExtension } ); +} + + wxString AllSymbolLibFilesWildcard() { return _( "All KiCad symbol library files" ) - + AddFileExtListToFilter( { KiCadSymbolLibFileExtension, "lib" } ); + + AddFileExtListToFilter( { KiCadSymbolLibFileExtension, + DatabaseLibraryFileExtension, + "lib" } ); } diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt index 5f64d349a2..abbe960690 100644 --- a/eeschema/CMakeLists.txt +++ b/eeschema/CMakeLists.txt @@ -274,6 +274,7 @@ set( EESCHEMA_SRCS sch_plugins/legacy/sch_legacy_lib_plugin_cache.cpp sch_plugins/legacy/sch_legacy_plugin.cpp sch_plugins/legacy/sch_legacy_plugin_helpers.cpp + sch_plugins/database/sch_database_plugin.cpp # Some simulation features must be built even if libngspice is not linked. sim/spice_settings.cpp diff --git a/eeschema/dialogs/panel_sym_lib_table.cpp b/eeschema/dialogs/panel_sym_lib_table.cpp index ba0567a982..f6fb58fcff 100644 --- a/eeschema/dialogs/panel_sym_lib_table.cpp +++ b/eeschema/dialogs/panel_sym_lib_table.cpp @@ -214,6 +214,7 @@ PANEL_SYM_LIB_TABLE::PANEL_SYM_LIB_TABLE( DIALOG_EDIT_LIBRARY_TABLES* aParent, P pluginChoices.Add( SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_KICAD ) ); pluginChoices.Add( SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_LEGACY ) ); + pluginChoices.Add( SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_DATABASE ) ); EESCHEMA_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings(); @@ -435,6 +436,9 @@ bool PANEL_SYM_LIB_TABLE::verifyTables() { SYMBOL_LIB_TABLE_ROW& row = dynamic_cast( table->At( r ) ); + if( !row.GetParent() ) + row.SetParent( table ); + if( !row.GetIsEnabled() ) continue; @@ -476,7 +480,8 @@ void PANEL_SYM_LIB_TABLE::browseLibrariesHandler( wxCommandEvent& event ) { wxString wildcards = AllSymbolLibFilesWildcard() + "|" + KiCadSymbolLibFileWildcard() - + "|" + LegacySymbolLibFileWildcard(); + + "|" + LegacySymbolLibFileWildcard() + + "|" + DatabaseLibFileWildcard(); EESCHEMA_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings(); diff --git a/eeschema/lib_field.cpp b/eeschema/lib_field.cpp index 0641882da4..f78f97a21a 100644 --- a/eeschema/lib_field.cpp +++ b/eeschema/lib_field.cpp @@ -71,6 +71,7 @@ LIB_FIELD& LIB_FIELD::operator=( const LIB_FIELD& field ) m_id = field.m_id; m_name = field.m_name; m_parent = field.m_parent; + m_autoAdded = field.m_autoAdded; SetText( field.GetText() ); SetAttributes( field ); @@ -95,6 +96,8 @@ void LIB_FIELD::Init( int aId ) // template fieldsnames' initial visibility is controlled by the template fieldname config. if( aId == DATASHEET_FIELD || aId == FOOTPRINT_FIELD ) SetVisible( false ); + + m_autoAdded = false; } @@ -191,6 +194,7 @@ void LIB_FIELD::Copy( LIB_FIELD* aTarget ) const aTarget->CopyText( *this ); aTarget->SetAttributes( *this ); aTarget->SetParent( m_parent ); + aTarget->SetAutoAdded( IsAutoAdded() ); } diff --git a/eeschema/lib_field.h b/eeschema/lib_field.h index 1717845ae5..3c1971dfec 100644 --- a/eeschema/lib_field.h +++ b/eeschema/lib_field.h @@ -173,6 +173,9 @@ public: bool IsMandatory() const; + bool IsAutoAdded() const { return m_autoAdded; } + void SetAutoAdded( bool aAutoAdded ) { m_autoAdded = aAutoAdded; } + private: /** @@ -209,6 +212,7 @@ private: int m_id; ///< @see enum MANDATORY_FIELD_T wxString m_name; ///< Name (not the field text value itself, that is #EDA_TEXT::m_Text) + bool m_autoAdded; ///< Was this field automatically added to a LIB_SYMBOL? }; #endif // CLASS_LIBENTRY_FIELDS_H diff --git a/eeschema/sch_io_mgr.cpp b/eeschema/sch_io_mgr.cpp index bb87f0981a..bc77689573 100644 --- a/eeschema/sch_io_mgr.cpp +++ b/eeschema/sch_io_mgr.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #define FMT_UNIMPLEMENTED _( "Plugin \"%s\" does not implement the \"%s\" function." ) @@ -61,6 +62,7 @@ SCH_PLUGIN* SCH_IO_MGR::FindPlugin( SCH_FILE_T aFileType ) case SCH_ALTIUM: return new SCH_ALTIUM_PLUGIN(); case SCH_CADSTAR_ARCHIVE: return new CADSTAR_SCH_ARCHIVE_PLUGIN(); case SCH_EAGLE: return new SCH_EAGLE_PLUGIN(); + case SCH_DATABASE: return new SCH_DATABASE_PLUGIN(); default: return nullptr; } } @@ -89,6 +91,7 @@ const wxString SCH_IO_MGR::ShowType( SCH_FILE_T aType ) case SCH_ALTIUM: return wxString( wxT( "Altium" ) ); case SCH_CADSTAR_ARCHIVE: return wxString( wxT( "CADSTAR Schematic Archive" ) ); case SCH_EAGLE: return wxString( wxT( "EAGLE" ) ); + case SCH_DATABASE: return wxString( wxT( "Database" ) ); default: return wxString::Format( _( "Unknown SCH_FILE_T value: %d" ), aType ); } @@ -111,6 +114,8 @@ SCH_IO_MGR::SCH_FILE_T SCH_IO_MGR::EnumFromStr( const wxString& aType ) return SCH_CADSTAR_ARCHIVE; else if( aType == wxT( "EAGLE" ) ) return SCH_EAGLE; + else if( aType == wxT( "Database" ) ) + return SCH_DATABASE; // wxASSERT( blow up here ) diff --git a/eeschema/sch_io_mgr.h b/eeschema/sch_io_mgr.h index ab0033ace1..b455b54c23 100644 --- a/eeschema/sch_io_mgr.h +++ b/eeschema/sch_io_mgr.h @@ -34,6 +34,7 @@ class SCH_SHEET; class SCH_SCREEN; class SCH_PLUGIN; class SCHEMATIC; +class SYMBOL_LIB_TABLE; class KIWAY; class LIB_SYMBOL; class SYMBOL_LIB; @@ -59,6 +60,7 @@ public: SCH_ALTIUM, ///< Altium file format SCH_CADSTAR_ARCHIVE, ///< CADSTAR Schematic Archive SCH_EAGLE, ///< Autodesk Eagle file format + SCH_DATABASE, ///< KiCad database library // Add your schematic type here. SCH_FILE_UNKNOWN @@ -459,6 +461,12 @@ public: */ virtual const wxString& GetError() const; + /** + * Some library plugins need to have access to their parent library table. + * @param aTable is the table this plugin is registered within. + */ + virtual void SetLibTable( SYMBOL_LIB_TABLE* aTable ) {} + //----------------------------------------------------- diff --git a/eeschema/sch_plugins/database/sch_database_plugin.cpp b/eeschema/sch_plugins/database/sch_database_plugin.cpp new file mode 100644 index 0000000000..6204030f19 --- /dev/null +++ b/eeschema/sch_plugins/database/sch_database_plugin.cpp @@ -0,0 +1,323 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Jon Evans + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include + +#include + +#include +#include +#include +#include +#include + +#include "sch_database_plugin.h" + + +SCH_DATABASE_PLUGIN::SCH_DATABASE_PLUGIN() : + m_libTable( nullptr ), + m_settings(), + m_conn() +{ +} + + +SCH_DATABASE_PLUGIN::~SCH_DATABASE_PLUGIN() +{ +} + + +void SCH_DATABASE_PLUGIN::EnumerateSymbolLib( wxArrayString& aSymbolNameList, + const wxString& aLibraryPath, + const PROPERTIES* aProperties ) +{ + std::vector symbols; + EnumerateSymbolLib( symbols, aLibraryPath, aProperties ); + + for( LIB_SYMBOL* symbol : symbols ) + aSymbolNameList.Add( symbol->GetName() ); +} + + +void SCH_DATABASE_PLUGIN::EnumerateSymbolLib( std::vector& aSymbolList, + const wxString& aLibraryPath, + const PROPERTIES* aProperties ) +{ + wxCHECK_RET( m_libTable, "Database plugin missing library table handle!" ); + ensureSettings( aLibraryPath ); + ensureConnection(); + + bool powerSymbolsOnly = ( aProperties && + aProperties->find( SYMBOL_LIB_TABLE::PropPowerSymsOnly ) != + aProperties->end() ); + + for( const DATABASE_LIB_TABLE& table : m_settings->m_Tables ) + { + std::vector results; + + if( !m_conn->SelectAll( table.table, results ) ) + { + if( !m_conn->GetLastError().empty() ) + { + wxString msg = wxString::Format( _( "Error reading database table %s: %s" ), + table.table, m_conn->GetLastError() ); + THROW_IO_ERROR( msg ); + } + + continue; + } + + for( DATABASE_CONNECTION::ROW& result : results ) + { + if( !result.count( table.key_col ) ) + continue; + + wxString name( fmt::format( "{}/{}", table.name, + std::any_cast( result[table.key_col] ) ) ); + + LIB_SYMBOL* symbol = loadSymbolFromRow( name, table, result ); + + if( symbol && ( !powerSymbolsOnly || symbol->IsPower() ) ) + aSymbolList.emplace_back( symbol ); + } + } +} + + +LIB_SYMBOL* SCH_DATABASE_PLUGIN::LoadSymbol( const wxString& aLibraryPath, + const wxString& aAliasName, + const PROPERTIES* aProperties ) +{ + wxCHECK( m_libTable, nullptr ); + ensureSettings( aLibraryPath ); + ensureConnection(); + + const DATABASE_LIB_TABLE* table = nullptr; + + std::string tableName( aAliasName.BeforeFirst( '/' ).ToUTF8() ); + std::string symbolName( aAliasName.AfterFirst( '/' ).ToUTF8() ); + + for( const DATABASE_LIB_TABLE& tableIter : m_settings->m_Tables ) + { + if( tableIter.table == tableName ) + { + table = &tableIter; + break; + } + } + + if( !table ) + { + wxLogTrace( traceDatabase, wxT( "LoadSymbol: table %s not found in config" ), tableName ); + return nullptr; + } + + DATABASE_CONNECTION::ROW result; + + if( !m_conn->SelectOne( tableName, std::make_pair( table->key_col, symbolName ), result ) ) + { + wxLogTrace( traceDatabase, wxT( "LoadSymbol: SelectOne (%s, %s) failed" ), table->key_col, + symbolName ); + return nullptr; + } + + return loadSymbolFromRow( symbolName, *table, result ); +} + + +bool SCH_DATABASE_PLUGIN::CheckHeader( const wxString& aFileName ) +{ + // TODO: Implement this sometime; but CheckHeader isn't even called... + return true; +} + + +void SCH_DATABASE_PLUGIN::ensureSettings( const wxString& aSettingsPath ) +{ + if( !m_settings ) + { + std::string path( aSettingsPath.ToUTF8() ); + m_settings = std::make_unique( path ); + m_settings->SetReadOnly( true ); + + if( !m_settings->LoadFromFile() ) + { + wxString msg = wxString::Format( + _( "Could not load database library: settings file %s missing or invalid" ), + aSettingsPath ); + + THROW_IO_ERROR( msg ); + } + } + else + { + wxASSERT_MSG( aSettingsPath == m_settings->GetFilename(), + "Path changed for database library without re-initializing plugin!" ); + } +} + + +void SCH_DATABASE_PLUGIN::ensureConnection() +{ + wxCHECK_RET( m_settings, "Call ensureSettings before ensureConnection!" ); + + if( !m_conn ) + { + if( m_settings->m_Source.connection_string.empty() ) + { + m_conn = std::make_unique( m_settings->m_Source.dsn, + m_settings->m_Source.username, + m_settings->m_Source.password, + m_settings->m_Source.timeout ); + } + else + { + std::string cs = m_settings->m_Source.connection_string; + std::string basePath( wxFileName( m_settings->GetFilename() ).GetPath().ToUTF8() ); + + // Database drivers that use files operate on absolute paths, so provide a mechanism + // for specifing on-disk databases that live next to the kicad_dbl file + boost::replace_all( cs, "${CWD}", basePath ); + + m_conn = std::make_unique( cs, m_settings->m_Source.timeout ); + } + + if( !m_conn->IsConnected() ) + { + wxString msg = wxString::Format( + _( "Could not load database library: could not connect to database %s (%s)" ), + m_settings->m_Source.dsn, + m_conn->GetLastError() ); + + THROW_IO_ERROR( msg ); + } + } +} + + +LIB_SYMBOL* SCH_DATABASE_PLUGIN::loadSymbolFromRow( const wxString& aSymbolName, + const DATABASE_LIB_TABLE& aTable, + const DATABASE_CONNECTION::ROW& aRow ) +{ + LIB_SYMBOL* symbol = nullptr; + + if( aRow.count( aTable.symbols_col ) ) + { + // TODO: Support multiple options for symbol + std::string symbolIdStr = std::any_cast( aRow.at( aTable.symbols_col ) ); + LIB_ID symbolId; + symbolId.Parse( std::any_cast( aRow.at( aTable.symbols_col ) ) ); + + LIB_SYMBOL* originalSymbol = m_libTable->LoadSymbol( symbolId ); + + if( originalSymbol ) + { + wxLogTrace( traceDatabase, wxT( "loadSymbolFromRow: found original symbol '%s'" ), + symbolIdStr ); + symbol = originalSymbol->Duplicate(); + } + else + { + wxLogTrace( traceDatabase, wxT( "loadSymboFromRow: source symbol '%s' not found, " + "will create empty symbol" ), symbolIdStr ); + } + } + + if( !symbol ) + { + // Actual symbol not found: return metadata only; error will be indicated in the + // symbol chooser + symbol = new LIB_SYMBOL( aSymbolName ); + } + else + { + symbol->SetName( aSymbolName ); + } + + if( aRow.count( aTable.footprints_col ) ) + { + // TODO: Support multiple footprint choices + std::string footprints = std::any_cast( aRow.at( aTable.footprints_col ) ); + wxString footprint = wxString( footprints.c_str(), wxConvUTF8 ).BeforeFirst( ';' ); + symbol->GetFootprintField().SetText( footprint ); + } + else + { + wxLogTrace( traceDatabase, wxT( "loadSymboFromRow: footprint field %s not found." ), + aTable.footprints_col ); + } + + for( const DATABASE_FIELD_MAPPING& mapping : aTable.fields ) + { + if( !aRow.count( mapping.column ) ) + { + wxLogTrace( traceDatabase, wxT( "loadSymboFromRow: field %s not found in result" ), + mapping.column ); + continue; + } + + wxString value( std::any_cast( aRow.at( mapping.column ) ).c_str(), + wxConvUTF8 ); + + if( mapping.name == wxT( "ki_description" ) ) + { + symbol->SetDescription( value ); + continue; + } + else if( mapping.name == wxT( "ki_keywords" ) ) + { + symbol->SetKeyWords( value ); + continue; + } + else if( mapping.name == wxT( "ki_fp_filters" ) ) + { + // TODO: Handle this here? + continue; + } + else if( mapping.name == wxT( "Value" ) ) + { + LIB_FIELD& field = symbol->GetValueField(); + field.SetText( value ); + field.SetVisible( mapping.visible_on_add ); + continue; + } + else if( mapping.name == wxT( "Datasheet" ) ) + { + LIB_FIELD& field = symbol->GetDatasheetField(); + field.SetText( value ); + field.SetVisible( mapping.visible_on_add ); + + if( mapping.visible_on_add ) + field.SetAutoAdded( true ); + + continue; + } + + LIB_FIELD* field = new LIB_FIELD( symbol->GetNextAvailableFieldId() ); + field->SetName( mapping.name ); + field->SetText( value ); + field->SetVisible( mapping.visible_on_add ); + field->SetAutoAdded( true ); + + symbol->AddField( field ); + } + + return symbol; +} \ No newline at end of file diff --git a/eeschema/sch_plugins/database/sch_database_plugin.h b/eeschema/sch_plugins/database/sch_database_plugin.h new file mode 100644 index 0000000000..dcd2331869 --- /dev/null +++ b/eeschema/sch_plugins/database/sch_database_plugin.h @@ -0,0 +1,106 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Jon Evans + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef KICAD_SCH_DATABASE_PLUGIN_H +#define KICAD_SCH_DATABASE_PLUGIN_H + +#include +#include +#include + + +class DATABASE_LIB_SETTINGS; +struct DATABASE_LIB_TABLE; + + +/** + * A KiCad database library provides both symbol and footprint metadata, so there are "shim" plugins + * on both the symbol and footprint side of things that expose the database contents to the + * schematic and board editors. The architecture of these is slightly different from the other + * plugins because the backing file is just a configuration file rather than something that + * contains symbol or footprint data. + */ +class SCH_DATABASE_PLUGIN : public SCH_PLUGIN +{ +public: + + SCH_DATABASE_PLUGIN(); + virtual ~SCH_DATABASE_PLUGIN(); + + const wxString GetName() const override + { + return wxT( "Database library" ); + } + + const wxString GetLibraryFileExtension() const override + { + return DatabaseLibraryFileExtension; + } + + const wxString GetFileExtension() const override + { + wxFAIL_MSG( "Database libraries are not schematic files! Fix call site." ); + return DatabaseLibraryFileExtension; + } + + int GetModifyHash() const override { return 0; } + + void EnumerateSymbolLib( wxArrayString& aSymbolNameList, + const wxString& aLibraryPath, + const PROPERTIES* aProperties = nullptr ) override; + + void EnumerateSymbolLib( std::vector& aSymbolList, + const wxString& aLibraryPath, + const PROPERTIES* aProperties = nullptr ) override; + + LIB_SYMBOL* LoadSymbol( const wxString& aLibraryPath, const wxString& aAliasName, + const PROPERTIES* aProperties = nullptr ) override; + + bool CheckHeader( const wxString& aFileName ) override; + + // Database libraries can never be written using the symbol editing API + bool IsSymbolLibWritable( const wxString& aLibraryPath ) override + { + return false; + } + + void SetLibTable( SYMBOL_LIB_TABLE* aTable ) override + { + m_libTable = aTable; + } + +private: + void ensureSettings( const wxString& aSettingsPath ); + + void ensureConnection(); + + LIB_SYMBOL* loadSymbolFromRow( const wxString& aSymbolName, + const DATABASE_LIB_TABLE& aTable, + const DATABASE_CONNECTION::ROW& aRow ); + + SYMBOL_LIB_TABLE* m_libTable; + + std::unique_ptr m_settings; + + std::unique_ptr m_conn; + +}; + +#endif //KICAD_SCH_DATABASE_PLUGIN_H diff --git a/eeschema/sch_symbol.cpp b/eeschema/sch_symbol.cpp index fa66ead617..50bbecf7a3 100644 --- a/eeschema/sch_symbol.cpp +++ b/eeschema/sch_symbol.cpp @@ -1018,7 +1018,8 @@ bool SCH_SYMBOL::ResolveTextVar( wxString* token, int aDepth ) const SCHEMATIC* schematic = Schematic(); // SCH_SYMOL object has no context outside a schematic. - wxCHECK( schematic, false ); + if( !schematic ) + return false; for( int i = 0; i < MANDATORY_FIELDS; ++i ) { diff --git a/eeschema/symbol_lib_table.cpp b/eeschema/symbol_lib_table.cpp index 5d281184bc..fe5553e7b7 100644 --- a/eeschema/symbol_lib_table.cpp +++ b/eeschema/symbol_lib_table.cpp @@ -76,6 +76,7 @@ bool SYMBOL_LIB_TABLE_ROW::Refresh() plugin.set( SCH_IO_MGR::FindPlugin( type ) ); SetLoaded( false ); + plugin->SetLibTable( static_cast( GetParent() ) ); plugin->EnumerateSymbolLib( dummyList, GetFullURI( true ), GetProperties() ); SetLoaded( true ); return true; @@ -312,7 +313,10 @@ SYMBOL_LIB_TABLE_ROW* SYMBOL_LIB_TABLE::FindRow( const wxString& aNickname, bool // instantiate a PLUGIN of the proper kind if it is not already in this // SYMBOL_LIB_TABLE_ROW. if( !row->plugin ) + { row->setPlugin( SCH_IO_MGR::FindPlugin( row->type ) ); + row->plugin->SetLibTable( this ); + } return row; } @@ -327,7 +331,7 @@ void SYMBOL_LIB_TABLE::LoadSymbolLib( std::vector& aSymbolList, wxString options = row->GetOptions(); if( aPowerSymbolsOnly ) - row->SetOptions( row->GetOptions() + " " + PropPowerSymsOnly ); + row->SetOptions( row->GetOptions() + " " + PropPowerSymsOnly ); row->SetLoaded( false ); row->plugin->EnumerateSymbolLib( aSymbolList, row->GetFullURI( true ), row->GetProperties() ); @@ -354,7 +358,7 @@ LIB_SYMBOL* SYMBOL_LIB_TABLE::LoadSymbol( const wxString& aNickname, const wxStr { SYMBOL_LIB_TABLE_ROW* row = FindRow( aNickname, true ); - if( !row || !row->plugin ) + if( !row || !row->plugin || !row->GetIsLoaded() ) return nullptr; LIB_SYMBOL* symbol = row->plugin->LoadSymbol( row->GetFullURI( true ), aSymbolName, diff --git a/eeschema/tools/sch_drawing_tools.cpp b/eeschema/tools/sch_drawing_tools.cpp index b93923b6a7..e385d53ffd 100644 --- a/eeschema/tools/sch_drawing_tools.cpp +++ b/eeschema/tools/sch_drawing_tools.cpp @@ -328,6 +328,9 @@ int SCH_DRAWING_TOOLS::PlaceSymbol( const TOOL_EVENT& aEvent ) addSymbol( symbol ); annotate(); + if( m_frame->eeconfig()->m_AutoplaceFields.enable ) + symbol->AutoplaceFields( /* aScreen */ nullptr, /* aManual */ false ); + // Update cursor now that we have a symbol setCursor(); } diff --git a/eeschema/widgets/symbol_preview_widget.cpp b/eeschema/widgets/symbol_preview_widget.cpp index 8afd85f443..87bbab1dbb 100644 --- a/eeschema/widgets/symbol_preview_widget.cpp +++ b/eeschema/widgets/symbol_preview_widget.cpp @@ -196,6 +196,18 @@ void SYMBOL_PREVIEW_WIDGET::DisplaySymbol( const LIB_ID& aSymbolID, int aUnit, i // This will flatten derived parts so that the correct final symbol can be shown. m_previewItem = symbol.release(); + // Hide fields that were added automatically by the library (for example, when using + // database libraries) as they don't have a valid position yet, and we don't support + // autoplacing fields on library symbols yet. + std::vector previewFields; + m_previewItem->GetFields( previewFields ); + + for( LIB_FIELD* field : previewFields ) + { + if( field->IsAutoAdded() ) + field->SetVisible( false ); + } + // If unit isn't specified for a multi-unit part, pick the first. (Otherwise we'll // draw all of them.) settings->m_ShowUnit = ( m_previewItem->IsMulti() && aUnit == 0 ) ? 1 : aUnit; diff --git a/include/database/database_connection.h b/include/database/database_connection.h new file mode 100644 index 0000000000..387777d275 --- /dev/null +++ b/include/database/database_connection.h @@ -0,0 +1,109 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Jon Evans + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef KICAD_DATABASE_CONNECTION_H +#define KICAD_DATABASE_CONNECTION_H + +#include +#include +#include +#include + +extern const char* const traceDatabase; + + +namespace nanodbc +{ + class connection; +} + + +class DATABASE_CONNECTION +{ +public: + static const long DEFAULT_TIMEOUT = 10; + + typedef std::map ROW; + + DATABASE_CONNECTION( const std::string& aDataSourceName, const std::string& aUsername, + const std::string& aPassword, int aTimeoutSeconds = DEFAULT_TIMEOUT, + bool aConnectNow = true ); + + DATABASE_CONNECTION( const std::string& aConnectionString, + int aTimeoutSeconds = DEFAULT_TIMEOUT, + bool aConnectNow = true ); + + ~DATABASE_CONNECTION(); + + bool Connect(); + + bool Disconnect(); + + bool IsConnected() const; + + /** + * Retrieves a single row from a database table. Table and column names are cached when the + * connection is created, so schema changes to the database won't be recognized unless the + * connection is recreated. + * @param aTable the name of a table in the database + * @param aWhere column to search, and the value to search for + * @param aResult will be filled with a row in the database if one was found + * @return true if aResult was filled; false otherwise + */ + bool SelectOne( const std::string& aTable, const std::pair& aWhere, + ROW& aResult ); + + /** + * Retrieves all rows from a database table. + * @param aTable the name of a table in the database + * @param aResults will be filled with all rows in the table + * @return true if the query succeeded and at least one ROW was found, false otherwise + */ + bool SelectAll( const std::string& aTable, std::vector& aResults ); + + std::string GetLastError() const { return m_lastError; } + +private: + bool syncTables(); + + bool cacheColumns(); + + bool getQuoteChar(); + + std::unique_ptr m_conn; + + std::string m_dsn; + std::string m_user; + std::string m_pass; + std::string m_connectionString; + + std::string m_lastError; + + std::map m_tables; + + /// Map of table -> map of column name -> data type + std::map> m_columnCache; + + long m_timeout; + + char m_quoteChar; +}; + +#endif //KICAD_DATABASE_CONNECTION_H diff --git a/include/database/database_lib_settings.h b/include/database/database_lib_settings.h new file mode 100644 index 0000000000..dbb994914e --- /dev/null +++ b/include/database/database_lib_settings.h @@ -0,0 +1,94 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Jon Evans + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef KICAD_DATABASE_LIB_SETTINGS_H +#define KICAD_DATABASE_LIB_SETTINGS_H + +#include + + +enum class DATABASE_SOURCE_TYPE +{ + ODBC, + INVALID +}; + + +struct DATABASE_SOURCE +{ + DATABASE_SOURCE_TYPE type; + std::string dsn; + std::string username; + std::string password; + int timeout; + std::string connection_string; +}; + + +struct DATABASE_FIELD_MAPPING +{ + std::string column; ///< Database column name + std::string name; ///< KiCad field name + bool visible_on_add; ///< Whether to show the field when placing the symbol + bool visible_in_chooser; ///< Whether the column is shown by default in the chooser +}; + + +/** + * A database library table will be mapped to a sub-library provided by the database library entry + * in the KiCad symbol/footprint library table. A single database library config file (managed by + * this class) may contain more than one table mapping, and each will have its own nickname. + * + * The LIB_ID for parts placed from this library will be constructed from the nickname of the + * database library itself, plus the nickname of the particular sub-library and the value of the + * key column for the placed part. + * + * For example, if a database library is configured with the nickname "PartsDB" and it provides a + * table called "Capacitors", with `key_col` set to "Part Number", the LIB_ID for a capacitor placed + * from this table will look something like `PartsDB-Capacitors:CAP-001` + */ +struct DATABASE_LIB_TABLE +{ + std::string name; ///< KiCad library nickname (will form part of the LIB_ID) + std::string table; ///< Database table to pull content from + std::string key_col; ///< Unique key column name (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 fields; +}; + + +class DATABASE_LIB_SETTINGS : public JSON_SETTINGS +{ +public: + DATABASE_LIB_SETTINGS( const std::string& aFilename ); + + virtual ~DATABASE_LIB_SETTINGS() {} + + DATABASE_SOURCE m_Source; + + std::vector m_Tables; + +protected: + wxString getFileExt() const override; +}; + +#endif //KICAD_DATABASE_LIB_SETTINGS_H diff --git a/include/fmt.h b/include/fmt.h new file mode 100644 index 0000000000..76f119e8d1 --- /dev/null +++ b/include/fmt.h @@ -0,0 +1,34 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Jon Evans + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef KICAD_FMT_H +#define KICAD_FMT_H + +#include +#include +#include + +// {fmt} formatter for wxString +FMT_BEGIN_NAMESPACE +template +struct formatter : basic_ostream_formatter {}; +FMT_END_NAMESPACE + +#endif //KICAD_FMT_H diff --git a/include/lib_table_base.h b/include/lib_table_base.h index 4dd9df688a..a2910b985b 100644 --- a/include/lib_table_base.h +++ b/include/lib_table_base.h @@ -43,6 +43,7 @@ class LIB_TABLE_LEXER; class LIB_ID; class LIB_TABLE_ROW; class LIB_TABLE_GRID; +class LIB_TABLE; class IO_ERROR; @@ -68,7 +69,8 @@ class LIB_TABLE_ROW : boost::noncopyable public: LIB_TABLE_ROW() : enabled( true ), - m_loaded( false ) + m_loaded( false ), + m_parent( nullptr ) { } @@ -77,11 +79,12 @@ public: } LIB_TABLE_ROW( const wxString& aNick, const wxString& aURI, const wxString& aOptions, - const wxString& aDescr = wxEmptyString ) : + const wxString& aDescr = wxEmptyString, LIB_TABLE* aParent = nullptr ) : nickName( aNick ), description( aDescr ), enabled( true ), - m_loaded( false ) + m_loaded( false ), + m_parent( aParent ) { properties.reset(); SetOptions( aOptions ); @@ -167,6 +170,10 @@ public: */ void SetDescr( const wxString& aDescr ) { description = aDescr; } + LIB_TABLE* GetParent() const { return m_parent; } + + void SetParent( LIB_TABLE* aParent ) { m_parent = aParent; } + /** * Return the constant #PROPERTIES for this library (#LIB_TABLE_ROW). These are * the "options" in a table. @@ -198,7 +205,8 @@ protected: options( aRow.options ), description( aRow.description ), enabled( aRow.enabled ), - m_loaded( aRow.m_loaded ) + m_loaded( aRow.m_loaded ), + m_parent( aRow.m_parent ) { if( aRow.properties ) properties = std::make_unique( *aRow.properties.get() ); @@ -225,6 +233,7 @@ private: bool enabled = true; ///< Whether the LIB_TABLE_ROW is enabled bool m_loaded = false; ///< Whether the LIB_TABLE_ROW is loaded + LIB_TABLE* m_parent; ///< Pointer to the table this row lives in (maybe null) std::unique_ptr< PROPERTIES > properties; }; @@ -514,8 +523,6 @@ protected: void ensureIndex() { - std::lock_guard lock( m_nickIndexMutex ); - // The dialog lib table editor may not maintain the nickIndex. // Lazy indexing may be required. To handle lazy indexing, we must enforce // that "nickIndex" is either empty or accurate, but never inaccurate. diff --git a/include/wildcards_and_files_ext.h b/include/wildcards_and_files_ext.h index 098a90e153..b7c331b869 100644 --- a/include/wildcards_and_files_ext.h +++ b/include/wildcards_and_files_ext.h @@ -128,6 +128,7 @@ extern const std::string GerberJobFileExtension; extern const std::string HtmlFileExtension; extern const std::string EquFileExtension; extern const std::string HotkeyFileExtension; +extern const std::string DatabaseLibraryFileExtension; extern const std::string ArchiveFileExtension; @@ -187,6 +188,7 @@ extern wxString DrawingSheetFileWildcard(); extern wxString SchematicSymbolFileWildcard(); extern wxString KiCadSymbolLibFileWildcard(); extern wxString LegacySymbolLibFileWildcard(); +extern wxString DatabaseLibFileWildcard(); extern wxString AllSymbolLibFilesWildcard(); extern wxString ProjectFileWildcard(); extern wxString LegacyProjectFileWildcard(); diff --git a/qa/data/dblib/database.sqlite b/qa/data/dblib/database.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..f6f15a2122c198a21937d6c0c752f3e10f396ee6 GIT binary patch literal 139264 zcmeI53z%NhxySd~-~F=p7D+@zBRk&hwn-oSwYxv*&5I_xH`0 z@z0uA@7g=_{{Qp#o!!}6tt{#4Ue?}QakQ;8#v6;0Dh(f>z8|KIR&KvbkRK=e zZEj!D-qG3H)!hSCy1BJw^8A*{{K->hw^T;JCq`687Yifiws-edW=tJXnBTJh{9U6L z=Cm(gwWPhHcU5<_d!tw9?hP>S=w*j>E$x99rge4ouITPu-n-FjQ>#54-JL6XJG+*{ z8~1Bpx~dBJH!YARFOQZY9w)R!MU0-q=EN~l# zIum`<@_pYpJ|R1(W!P8!;?`gxNJjIjP<{n2{gdfR&4deM5;dcu0t zdcgXbb%%Acb)9vkb%}L>b)NN2>vZcRt7fgWR;~YKeZMsMNLCtLzy{bFdsT=R@2~t zgZmF0)I53aSli}vR&}@cwoh-J!N>}+hZjo`aKQO zPSdRU<`0a|jJf6Smp&=YE`G1@QDLwAcXIFLre@F0yq(!IeOBs?)NcA2>=oumZ~upU z)S3pFLk5i*S~FU9?mxKSzHgZrZqO`|BUyOx*01wz=(#?Qwy>Cc3C)%i5xUMP>f9O7zUE5`EbUA64PwxxKK` z_b(f{7R(sd3H!*t&r}xjQGK7QHEn}w6*RRD#I$mnS_fcSZJJv9BU*VtQ|s24R-dNU zewbE|rq-=6tu9Ti7N%9v)M{c{B~7gcrj=;lDq~uS_N@}8m1y58Vp@sztpcLeCfc|1 zm{y{FD~D+%+PAWpR-%0?gJ~t&x6+taqJ1m+tNhLGw``()EBgEW&9!dUx48I++#5z; z_P@C|BZ^gfo>{r@;*-6YXHX)LCd=$^DIrj_WP zGzQa3bWhp^)4JK7MB{l?6Q*^uJ&A_a(U?}EXX%|0tq#$%^iG&oqG#z*m{y`^>5-UL zqJ3*eOe@j8H3HL0v~LZ^v=Z%G6-+D9zBLTfO0;k7fN3S#w}v8G1<}5>J*Jgt-x`8x zCEB;P!?Y6ZTZ1vJMElmZm{yW~i`M;r3$ku5(LHG@rj_WP)Qo8*x+hJ+v=ZHuCSzKO z?n!%MT1oCnwWd8Vtwhh#yJK33o~0*YT8W;ecf+(2JxfnSv?`)~YXYW~Xx|Djtwj5l zk7*^^w>(TM(Z1zkT8Z{8g=r<)w|3<%{|`deEg-rl9f)Zqx+figX(hTREkd;VME9iqF|9=Rq=lGPl6z9EX+KOW(X;dd zOe@i|^n6S!(X;eCOe@i|bStKnXy4iw(@L~&&Be45?OXdGT0NqDYi~>|(Y`eY(@L~& z&BnA6?OU@jtwj6QOiU}$zBL2WO0;k7g=r<)x29uSiT15&m{yW~i`M;r&*tmKInh0- z8`Da3Pg;p-CAufAz_b$Gle#diME9iSm{yW|QmttjqLu1=>QYQA(X;fCm{y`^=_4?$ zM9uZ=+qJ8UdOe@j8wFJ{jv~N{0twj6QVoWR1zSXh0Ry!d24CpXSE788y zj%g*@w+_X$675@Um{y{F>kv#U(Y|#srj=yhT7Umv`)dEc=Il9{H#57Xzma+=rSy~8 zvuvDJ`#-e*t2NaS=N#@3-HB?5bq;rkZbdc3JBK?&_o5nNp2HoYn^6sM&*2Wy-Kd7x z=WvJQb`cV_Jze zu;VbTMEln|Oe@j$bu6ZpX!lx+X(igcj={7N?OjJ>T8XxD^*YE)tFYI zeQOn_m1y7U#k7*_TkG%tS83Kf^HSqeV|Mw1(g&rM;<<&l3cKge%)OHHvR}_Um$B3J z)RUKw54u+)FaH>~gqIbqhlL za=1@*3qlNYxKDKpLL76rPjw4IEOR*3anu^(nZrG*hiJq!hkH~H(THmf_ozMwLTq!m zNA(bmWYutwY6nBYYPd(WgCS`(+@spTkhmJ|QSD$zUJduCb}%HchEp|zt|5sv+@;#V zkjNVDQtezs0{{A2F%n>%#Lo{NVBW$XN zXv8&#Q=LeyA+|X@pn8Z#vTArhwSysHH9Vl&!H~2X9#HLINL&pMsCF|k5!{vUD65oBjk5xX2gb`}-!%Mny(QEP}{ju2G0AjC082&!8UVwocZ$t@_l|3^G? zgrIteMoe>rpn8Z#Tyun=`WOhY%@Km?AsWf55rXO=8VRcrREJV)NLr2HR1eWeT#evV zI~bBzBRJI#h6L6Ks%FqNB(X+tsvQi8tPz}Q2SYMz1gF};kkA^zsdg|VwMKBN9Sn)B z5mYUqYe;U5ut|2XEp`8oxaA14bE$}3jvzaiiumOSvU913VU8d>mx?&%2(ojjh-Ho- zJC}-h<_NNLsfcNgAUl_exaJ75bE$}Jjvzaiie%LYvU90OSdAb%mx`p-2(ojjNL-B| zJC};&)d-q%snJ7WB(O%1wS`6!YXn(aXe6>mkhO(IGHV1`TWBP-Mv%3IMpA18SzBl% zwnmV(g+_8~1WjA$Ep-1MBDOh#>Rf7w*yaeTbEzR>nsrG|)Yj-Wc18X~qig6dpq zh}h-`s&lC!Vw)qV&ZUNkZH}Niml`6rIfCk3YKYk82&!|bA(C1nsLrK^NNSCsI+q$E zsWpP?Txy7<)(EO|sUebDBWTX0)|x^jwMJ02hz^m|8bQ?}Iz&=y1XYXZ5J{~OR4t-I zB(+9RwTKRp)EYt7A{vRU5o9f*k=zRf7w*yaeTbEzSc zU?Zr`rG`j?ji5T08X^fcg6dpqh$Pqus&lC!l3*if&ZS28|44$3plT5vA_+Evszr2& zB-jY57SSP+U?ZqnM2AR%ji71~9U=)ff~rMyh$Pqusus~9l3*ifT0|G4D8DB%*J#$q z)|=LI)??Pa)~(i6)`iyD)+yFHYo%4S7Flzw$(FL3tf5vvD`S3YzGJ>d&{?$uPR?yKD&HMd0lyBxmsRSo>QJ&R^_Jh z&~m?Wru1p)ozjb?CrS^L?kHVXx}GV>qw5rruI=HlNsihQ@#+HVc29^rN&x`LD zUoAdee7Jab@uuSC#q*127Edf5U0hadFD@v~C{8N!;;7=_qEXZe9~NFKJY9IGaA)EA z!X<@s3#S&2E37OmE-Wm}EKDlcg&hmq6bkvz^6%zf%0H36KYv^P>imWIv+^h9kI65~ zADW++pOz2uyXJ@Gx6WsBpXA=kJ)e6lcTeu-+!eX=b7$o0xz)MO+(Eg0a#M0DH##>Y zXXaS;qwMS1XR;4x@5wd!$5a zRBGE)N&iBBUw=h^N`FwlL%&wPSU*QUML$;W(ht)Y=zHlCqdz#Q-v4C~Wb(_C6-ZX# zf3O1O)DXS3W!~6mTvK}jKxn@P;M%VMZ0+$F>_B@Az}J2W;AxKnxY{ECN_!YUY7YT8 z+Ajcv_8Vw7UU>b{7EG?gX&4 zpTyt?+8qGCb~}Kl-3H)lKL$|RtpHNH1;Ej61`yhh0JwG&fUVsagBNHw0QlPV0G@Uo zfU8{#ptNfMq;@rcqg@3cv>yU+?MeV!yCMcR&@Kn?wI2X@+GPN)b}4|;E&-6*_W>O3 zVgR9C1i-Zm0c`DiF{nVh0KnJ23*c$z1Gw6E0F?G^0I7Woz|qbF5Zbu_T>DP|TRSHP z8E9t%_}W`*#4Yoep4Yr^Vm|+Nl7(_H_VHI|abi zP6klgNdQv&HvmUF5kP1S0It;mY%PpI1X>Ni*G?#U5%^De0E<#BBX%b@m`LhT(1 zwR;5AsuODYHK?7#p%zP^=2fWe#T#ln2s)tl4};okhuS?9YSjj{JOpazV5r4GQ1b(! zwhxHw=y@d6{{B#V3!!%RgIXvEdvg*Z(450~#M z-%!4^d|vsqLMi`6{{8$b`KR&^=I_W~o4+`JPX3hqvH7n2Vfh95z48`mDpWWSyL_iQcOn>{>xKz2@c&#aT(DLXh@&ipm=LFU!WZ!*8g{3LT-=KGod%zQnw zF0&%jk=ZXZBePp(d}c&uU?!jbOZpG#7t_B^|2+NU^i}EarN5c}xAf8JrRlbGYq}-v zr^lpsNcT&pQ-4bRKJ{Gcm#Lqnew4aA^_|o=QekRU>T9V3Q+uZ-r(|m9)OIN&rRg8) zujxQVZxv22oKWZ~EGaB1 z%r5Lv5QS0EACkX)Y&b7S?z8x$C%MPslb+-*i%)uzD;A&hB$q5c=}GRe_@pPfVDU*$ za?awDp5!)*PkPc0SbWlxw$I{|p0qs{pY){dviPJYZN=h~p0p*4PkPdJSbWlxwqWr| zPuiTtCp~G~EI#QuLBQgZo)h>iKIu7u$KsQo6Syos={bR7@k!4KB#TdaPT;Wkq~`>J z#V0){;4D7rIf2dMlb+)TEI#QuzR%*5p5uEgKIu8W%i@!s<0}@Q^c-KZ6E=Js9p7Qc z0|<5;0B7p}Y<6r6UclA@`0N+}j~xx*vNZsT9R(oSY5<3=0uZbhfU_O|n{~(F25cpO z&sG3}xTofE^Ctvn2o?s{*)eF@Rzn0FoUB z;IMW8!43uBtPQ|shr}QQb})d?4g&DlfdDQ$06?)t0Fvzw;IM@Ng6#*u*#ZEY&5ywe z*gOEAwE}o-UjUcQ1yF1s0Lk_SaM&CG!Da(+HVeRJGh+||n*rdny#PEm9l&MN02FHh zkZdY|!{kfMOE?B%1)>umC_XAAmCtz-Dd? z(VnOPd?o=r<^Z@%04T-*B(nh=HXcB*aR8i+1+dw!8zB0*JvIiwXS)D+tO>wnqX87# z89=g~030?7K(LVjob3o;vk@^wd*W~apH%=nHVnXJI{+v)6hN}=0US0YdAPly<0t=2 zR^b0<1>iqG!L|e7Y%qY$wv8b=hYSMn*){+k8wlXC0RW2i2as%Q0EhJh5Ns;|&MW|% znK4A?5CgzxWdM(r09;lCP^2jFNg00`}Q0Iod;U~9jPfd|^N0KWDNfTukT;A+1CP});l z>gWHF*c^wdFLj8-<~US+sY4_-$D!&=9U`$g4pm?35Q)uksQOZeNNkQn)t5R%Vsjj- zzSJQSo8wURr4EtU9EYkeb%?~~I8=S9L*E=SXw1->(Xw;@!Tkp2`<|yeRDG#K4--q& zmpXJYu|$2TLxqT?>PsC;Oe{%X>gebH9ZW3IWQ9;*Vu>azgd7t~G#eJW*|aj5!Ihe&LW zL)Di$L}GIss=m}A5}V^t^`#Dx*c^wdFLj8-<~US+sY4_-$D!&=9U`$g4pm?3a37?{ zQ}v||_r}ChJw(sJ#8N#(&&I@3Jw(sK#8N#(&&0%%JVZwiD`p^KsYXPHdtqX!J_eeO ziKY4&Xc{J#>SLf5Of1#MKvOZXR6AHRCYEFe+fw)cNNkQn)|VQI&2h;3QX{cB4q0Dn zBsRw(>r0Kq<~U@1sgc+mhpaC(5}V_Y^`%B)a~!h1)JSZOL)MoXiOq4y`cf}JdOTTQ z>MACd>LGeDCYI_Ux&sqS^$>j+CYI_Ux*Zcs@(>-}{~wBprTQ4C4HHZCG0-8HSgMbK z4o1XMjff5p!o*U240IqSmTCt(0252HgKeSv|2h(z6U{s?YCpW%)!J;kwll4DtnCSV z*6gX()tw#H39YTwp3a`$uI{#ZbEZycZR>ka8+UDgPPMnYvt#0Bue8l=Uu=&H{58=< zEnC(W{eWg={-vWbdCVsoOIaiU_8*qmslov2tOHYb|7Cn^?+&51k#h>AsGb0W_IqGFNQoXAsw zrq~dP&51l8h>AsGa~!h1)a#HQPu7?ESWGO@4i>IO#FF);J_Zv@w1b65V`7PRuy73~ zmS_hHkHW+f?O@?*Of1z7wh9wVwS)CyVyQj`>cPYky$1`sF|kxT*h)k!&4}nNb^njV z=0x^zG|#1VBsM3quOliJiOq@Z^@xf^Vsj$NBHYc)ABq|n(&57(C ziHb#Hb0YgmqGFNQoX8%Ns8}R6C$jG(Dt5r;JwCD*B`UT*CYEXk+Zq!~wS)D;#8T~G zTVZ0Ub}(ymv61~Q(f`0qOf1z7W?*8ecCa!gmTCtpVPdIvup%awY6mM|VySkpJSLW8 z2isEj|A=jlq&k;cM{IK>)w$F5$xlq93q9 zY;&YT^$?BN=17O?AsVsGkyPhW>xgZR6jTqa*cQcyia?}CY?dWdeq#8N#(kH*AO z?O;1&VySkpoiMRfJJ={pEY%J+5)(_cgYAfjrD_pfAAyOb+QEinVySkp3MQ6n2OEZo zCE3BY)crqVnwmDK!Jwzk6IZ{zQL?gC2Qc*obBMCNAQ9VQ>2{w}ITxuOju#uALAsR`r zk&-| zY7t#W5^N+@i|9I%U?XW-L~p74f5bLNQk_e!Bepq`>Rf6avCWZG=Ths4ZI1M*9--|Y7t#W5^N+@i|9I%U?ZtoMAwl7 z8%fn7x{f5+NU9dmbtJ(?QniS#BMCN=szr1iNwASLEuy#7{Xb%xqsY#sBDOh->|82h zo1@6ir6RUD%BFgVMr?DGP4y6s*ybpk>LD7j%~3YhLo{NWBgxLCBDOg)p!ygHvCWYI z)yF_cf{hHQJ_bS(Y-B+7F%XhqBLk|Bfsh0n8BpzDNP>+FsCF`Cr8W@T97T05wSm~?C_(fP-9T(}lpuPDZXmWfN)SCnHxSz#C5RrP8;EU=5=0Nt z4a7D_38IgI8i;L<5=0*ZHIM`wMRhKeRg35bl3=4~T10QD`+vkXM^T+iZ6LNeit1cy1F_9fROeC~h;5Fd zI+xl&Y;zRVxzq+?o1>`Cr8W@T97T05wSm~?D5`U*4a7D_QJqU|AhtP*>Rf6ANw85= z=TaL;f{mg&m)bxQY!ubG)CQ7Zqo~fMHjo4xMRP7Sy8lNKY!p?C=mwHtqo`U$H;@Dy zMb#p@fh5={sus}=B*8{ewTNyY2{wwVMRWs6uu)Vkq8mtpjiPB0UDPz~qTGF&^`Z5e z^|bYnb*FW`b%}MZb*goowbEK_EwpA@lPufX(b~o;n4g*NnlG78nD?8vnOB<^nrE3O zna7yR%tOt2<}@=fcQuEZTbmi<6XPx8dE+tT9^+=?3gdj^45MzWHad-ijD3tLhB8JQ zLk!bk<&Vm*m!By=T)wM(L;2G3dF9i}$Ctay)$;!3S>@f!ygafzs9Y?4UV5+ea_Py^ z1Et$b*OV?Qon1P)w6?Uo)LxojnqHbv8e6KA`j@iBPm6CCUno9aytjBu@ygbd zg;9lV3#I%Q`Sr((ceKLTn z?+KvvJpiP>JAk840ucIc09>C4VCxfNa05L6@O2-+(>(xJcL9{H0HiJf9NhsBx&Yuh z2e5TJ1{LVz0epQNfTxcIaP?gQls*PP>bn3qdJ}-qM+0zuX8>E@DFzwnqX2w;B!H*y z2;k}?0F*u)KNUZUYeP#{it&3ShHaVsHX>Gl0*21mLlo09fMPcQNOnDd z!>$7m>{GT?OE?9|CynN&uH#0if9B0FwOxz+smG2zDs|XO{rj?E8Ix z|DP>=pjq?GON~#B+2so&NIsGkNLCsH6p5kB-kpd5m5~!!B$a?h-x4Swu)**R0Bz{RWu`_ F{tLTtsJQ?D literal 0 HcmV?d00001 diff --git a/qa/data/dblib/qa_dblib.kicad_dbl b/qa/data/dblib/qa_dblib.kicad_dbl new file mode 100644 index 0000000000..d77d3bcb57 --- /dev/null +++ b/qa/data/dblib/qa_dblib.kicad_dbl @@ -0,0 +1,114 @@ +{ + "meta": { + "version": 0, + "filename": "qa_dblib.kicad_dbl" + }, + "name": "QA Database", + "description": "A database for testing purposes", + "source": { + "type": "odbc", + "dsn": "", + "username": "", + "password": "", + "timeout_seconds": 2, + "connection_string": "Driver={SQLite3};Database=${CWD}/database.sqlite" + }, + "libraries": [ + { + "name": "Resistors", + "table": "Resistors", + "key": "Part ID", + "symbols": "Symbols", + "footprints": "Footprints", + "fields": [ + { + "column": "Manufacturer", + "name": "Manufacturer", + "visible_on_add": false, + "visible_in_chooser": false + }, + { + "column": "MPN", + "name": "MPN", + "visible_on_add": false, + "visible_in_chooser": true + }, + { + "column": "Value", + "name": "Value", + "visible_on_add": true, + "visible_in_chooser": true + }, + { + "column": "Resistance", + "name": "Resistance", + "visible_on_add": false, + "visible_in_chooser": false + }, + { + "column": "Resistance Tolerance", + "name": "Tolerance", + "visible_on_add": false, + "visible_in_chooser": false + }, + { + "column": "Power Rating", + "name": "Power Rating", + "visible_on_add": true, + "visible_in_chooser": false + }, + { + "column": "Description", + "name": "ki_description", + "visible_on_add": false, + "visible_in_chooser": true + } + ] + }, + { + "name": "Capacitors", + "table": "Capacitors", + "key": "Part ID", + "symbols": "Symbols", + "footprints": "Footprints", + "fields": [ + { + "column": "Manufacturer", + "name": "Manufacturer", + "visible_on_add": false, + "visible_in_chooser": false + }, + { + "column": "MPN", + "name": "MPN", + "visible_on_add": false, + "visible_in_chooser": true + }, + { + "column": "Value", + "name": "Value", + "visible_on_add": true, + "visible_in_chooser": true + }, + { + "column": "Dielectric Type", + "name": "Dielectric", + "visible_on_add": true, + "visible_in_chooser": false + }, + { + "column": "Voltage Rating", + "name": "Voltage Rating", + "visible_on_add": true, + "visible_in_chooser": true + }, + { + "column": "Description", + "name": "ki_description", + "visible_on_add": false, + "visible_in_chooser": true + } + ] + } + ] +} \ No newline at end of file diff --git a/qa/unittests/common/CMakeLists.txt b/qa/unittests/common/CMakeLists.txt index f6027070e2..467a371f07 100644 --- a/qa/unittests/common/CMakeLists.txt +++ b/qa/unittests/common/CMakeLists.txt @@ -54,6 +54,10 @@ set( QA_COMMON_SRCS view/test_zoom_controller.cpp ) +if( KICAD_TEST_DATABASE_LIBRARIES ) + set( QA_COMMON_SRCS ${QA_COMMON_SRCS} test_database.cpp ) +endif() + set( QA_COMMON_LIBS common libcontext @@ -88,4 +92,10 @@ include_directories( ${INC_AFTER} ) +if( KICAD_TEST_DATABASE_LIBRARIES ) + set_source_files_properties( test_database.cpp PROPERTIES + COMPILE_DEFINITIONS "QA_DATABASE_FILE_LOCATION=(\"${CMAKE_SOURCE_DIR}/qa/data/dblib\")" + ) +endif() + kicad_add_boost_test( qa_common qa_common ) diff --git a/qa/unittests/common/test_database.cpp b/qa/unittests/common/test_database.cpp new file mode 100644 index 0000000000..8e2e99f8a6 --- /dev/null +++ b/qa/unittests/common/test_database.cpp @@ -0,0 +1,69 @@ +/* +* This program source code file is part of KiCad, a free EDA CAD application. +* +* Copyright (C) 2022 Jon Evans +* Copyright (C) 2022 KiCad Developers, see AUTHORS.TXT for contributors. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, you may find one here: +* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html +* or you may search the http://www.gnu.org website for the version 2 license, +* or you may write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +*/ + +#include +#include + +#include + +BOOST_AUTO_TEST_SUITE( Database ) + + +BOOST_AUTO_TEST_CASE( Connect ) +{ + std::string cs = fmt::format( "Driver={{SQLite3}};Database={}/database.sqlite", + QA_DATABASE_FILE_LOCATION ); + + // Construct and connect + DATABASE_CONNECTION dc( cs, 2, false ); + BOOST_CHECK_NO_THROW( dc.Connect() ); + BOOST_CHECK( dc.IsConnected() ); + + dc.Disconnect(); + BOOST_CHECK( !dc.IsConnected() ); + + dc.Connect(); + BOOST_CHECK( dc.IsConnected() ); + + dc.Disconnect(); + + // Scoped connection should self-disconnect + { + DATABASE_CONNECTION dc2( cs, 2 ); + } + + dc.Connect(); + BOOST_CHECK( dc.IsConnected() ); + + DATABASE_CONNECTION::ROW result; + + BOOST_CHECK( dc.SelectOne( "Resistors", std::make_pair( "Part ID", "RES-001" ), result ) ); + + BOOST_CHECK( !result.empty() ); + BOOST_CHECK( result.count( "MPN" ) ); + BOOST_CHECK_NO_THROW( std::any_cast( result.at( "MPN" ) ) ); + BOOST_CHECK_EQUAL( std::any_cast( result.at( "MPN" ) ), "RC0603FR-0710KL" ); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/thirdparty/nanodbc/CMakeLists.txt b/thirdparty/nanodbc/CMakeLists.txt index f90777d49e..9fc15f2ed0 100644 --- a/thirdparty/nanodbc/CMakeLists.txt +++ b/thirdparty/nanodbc/CMakeLists.txt @@ -73,10 +73,10 @@ endif() message(STATUS "nanodbc feature: Disable async features - ${NANODBC_DISABLE_ASYNC}") if(NANODBC_ENABLE_UNICODE) - add_definitions(-DNANODBC_ENABLE_UNICODE) + add_compile_definitions(NANODBC_ENABLE_UNICODE) if(MSVC) # Sets "Use Unicode Character Set" property in Visual Studio projects - add_definitions(-DUNICODE -D_UNICODE) + add_compile_definitions(UNICODE _UNICODE) endif() endif() message(STATUS "nanodbc feature: Enable Unicode - ${NANODBC_ENABLE_UNICODE}") @@ -179,16 +179,27 @@ endif() ######################################## ## library target ######################################## -add_library(nanodbc nanodbc/nanodbc.cpp nanodbc/nanodbc.h) +add_library(nanodbc + STATIC + nanodbc/nanodbc.cpp + nanodbc/nanodbc.h) target_link_libraries(nanodbc ${Boost_LIBRARIES} ${ODBC_LIBRARIES}) -target_include_directories(nanodbc PUBLIC +if(APPLE) + target_link_libraries(nanodbc ${ODBC_LINK_FLAGS}) +endif() + +target_include_directories(nanodbc PUBLIC SYSTEM $ $) # /include/nanodbc if(UNIX) set_target_properties(nanodbc PROPERTIES - COMPILE_FLAGS "${ODBC_CFLAGS}" - LIBRARY_OUTPUT_DIRECTORY "lib") + COMPILE_FLAGS "${ODBC_CFLAGS}") endif() + +if(NANODBC_ENABLE_UNICODE) + add_compile_definitions(NANODBC_ENABLE_UNICODE) + target_compile_definitions(nanodbc PUBLIC NANODBC_ENABLE_UNICODE) +endif() \ No newline at end of file