From 2b94b7df77f6a237c7dcdda79c2a0bcf906ba7c2 Mon Sep 17 00:00:00 2001
From: Jon Evans <jon@craftyjon.com>
Date: Sat, 10 Dec 2022 16:35:11 -0500
Subject: [PATCH] DbLibs: Support empty virtual table names

Fixes https://gitlab.com/kicad/code/kicad/-/issues/12902
---
 .../database/sch_database_plugin.cpp          | 75 +++++++++++++------
 eeschema/symbol_tree_model_adapter.cpp        |  4 +-
 2 files changed, 57 insertions(+), 22 deletions(-)

diff --git a/eeschema/sch_plugins/database/sch_database_plugin.cpp b/eeschema/sch_plugins/database/sch_database_plugin.cpp
index 1e94f2aaba..0fbd519328 100644
--- a/eeschema/sch_plugins/database/sch_database_plugin.cpp
+++ b/eeschema/sch_plugins/database/sch_database_plugin.cpp
@@ -90,7 +90,8 @@ void SCH_DATABASE_PLUGIN::EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolL
             if( !result.count( table.key_col ) )
                 continue;
 
-            wxString name( fmt::format( "{}/{}", table.name,
+            std::string prefix = table.name.empty() ? "" : fmt::format( "{}/", table.name );
+            wxString name( fmt::format( "{}{}", prefix,
                                         std::any_cast<std::string>( result[table.key_col] ) ) );
 
             LIB_SYMBOL* symbol = loadSymbolFromRow( name, table, result );
@@ -110,36 +111,60 @@ LIB_SYMBOL* SCH_DATABASE_PLUGIN::LoadSymbol( const wxString&   aLibraryPath,
     ensureSettings( aLibraryPath );
     ensureConnection();
 
-    const DATABASE_LIB_TABLE* table = nullptr;
+    /*
+     * Table names are tricky, in order to allow maximum flexibility to the user.
+     * The slash character is used as a separator between a table name and symbol name, but symbol
+     * names may also contain slashes and table names may now also be empty (which results in the
+     * slash being dropped in the symbol name when placing a new symbol).  So, if a slash is found,
+     * we check if the string before the slash is a valid table name.  If not, we assume the table
+     * name is blank if our config has an entry for the null table.
+     */
 
-    std::string tableName( aAliasName.BeforeFirst( '/' ).ToUTF8() );
-    std::string symbolName( aAliasName.AfterFirst( '/' ).ToUTF8() );
+    std::string tableName = "";
+    std::string symbolName( aAliasName.ToUTF8() );
+
+    if( aAliasName.Contains( '/' ) )
+    {
+        tableName = std::string( aAliasName.BeforeFirst( '/' ).ToUTF8() );
+        symbolName = std::string( aAliasName.AfterFirst( '/' ).ToUTF8() );
+    }
+
+    std::vector<const DATABASE_LIB_TABLE*> tablesToTry;
 
     for( const DATABASE_LIB_TABLE& tableIter : m_settings->m_Tables )
     {
         if( tableIter.name == tableName )
+            tablesToTry.emplace_back( &tableIter );
+    }
+
+    if( tablesToTry.empty() )
+    {
+        wxLogTrace( traceDatabase, wxT( "LoadSymbol: table '%s' not found in config" ), tableName );
+        return nullptr;
+    }
+
+    const DATABASE_LIB_TABLE* foundTable = nullptr;
+    DATABASE_CONNECTION::ROW result;
+
+    for( const DATABASE_LIB_TABLE* table : tablesToTry )
+    {
+        if( m_conn->SelectOne( table->table, std::make_pair( table->key_col, symbolName ),
+                               result ) )
         {
-            table = &tableIter;
-            break;
+            foundTable = table;
+            wxLogTrace( traceDatabase, wxT( "LoadSymbol: SelectOne (%s, %s) found in %s" ),
+                        table->key_col, symbolName, table->table );
+        }
+        else
+        {
+            wxLogTrace( traceDatabase, wxT( "LoadSymbol: SelectOne (%s, %s) failed for table %s" ),
+                        table->key_col, symbolName, table->table );
         }
     }
 
-    if( !table )
-    {
-        wxLogTrace( traceDatabase, wxT( "LoadSymbol: table %s not found in config" ), tableName );
-        return nullptr;
-    }
+    wxCHECK( foundTable, nullptr );
 
-    DATABASE_CONNECTION::ROW result;
-
-    if( !m_conn->SelectOne( table->table, std::make_pair( table->key_col, symbolName ), result ) )
-    {
-        wxLogTrace( traceDatabase, wxT( "LoadSymbol: SelectOne (%s, %s) failed" ), table->key_col,
-                    symbolName );
-        return nullptr;
-    }
-
-    return loadSymbolFromRow( aAliasName, *table, result );
+    return loadSymbolFromRow( aAliasName, *foundTable, result );
 }
 
 
@@ -149,8 +174,16 @@ void SCH_DATABASE_PLUGIN::GetSubLibraryNames( std::vector<wxString>& aNames )
 
     aNames.clear();
 
+    std::set<wxString> tableNames;
+
     for( const DATABASE_LIB_TABLE& tableIter : m_settings->m_Tables )
+    {
+        if( tableNames.count( tableIter.name ) )
+            continue;
+
         aNames.emplace_back( tableIter.name );
+        tableNames.insert( tableIter.name );
+    }
 }
 
 
diff --git a/eeschema/symbol_tree_model_adapter.cpp b/eeschema/symbol_tree_model_adapter.cpp
index 5afdedffb7..00a98de0ae 100644
--- a/eeschema/symbol_tree_model_adapter.cpp
+++ b/eeschema/symbol_tree_model_adapter.cpp
@@ -153,7 +153,9 @@ bool SYMBOL_TREE_MODEL_ADAPTER::AddLibraries( const std::vector<wxString>& aNick
 
                 for( const wxString& lib : subLibraries )
                 {
-                    wxString name = wxString::Format( wxT( "%s - %s" ), pair.first, lib );
+                    wxString suffix = lib.IsEmpty() ? wxT( "" )
+                                                    : wxString::Format( wxT( " - %s" ), lib );
+                    wxString name = wxString::Format( wxT( "%s%s" ), pair.first, suffix );
                     wxString desc;
 
                     if( !parentDesc.IsEmpty() )