You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by ab...@apache.org on 2019/09/02 13:36:58 UTC

[cayenne] 01/05: CAY-2612 Modeler: add lazy-loading to dbImport tab

This is an automated email from the ASF dual-hosted git repository.

abulatski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 9fa986f437d0b257ce3ac853975bb1ec73c54a0b
Author: Arseni Bulatski <an...@gmail.com>
AuthorDate: Wed Aug 28 15:53:43 2019 +0300

    CAY-2612 Modeler: add lazy-loading to dbImport tab
---
 RELEASE-NOTES.txt                                  |   1 +
 .../org/apache/cayenne/dba/mysql/MySQLAdapter.java |   4 +-
 .../cayenne/dba/postgres/PostgresAdapter.java      |  32 ++--
 .../cayenne/dba/sqlserver/SQLServerAdapter.java    |   5 +-
 .../cayenne/modeler/action/LoadDbSchemaAction.java |  69 +++++++--
 .../editor/dbimport/DatabaseSchemaLoader.java      | 167 ++++++++++++---------
 .../modeler/editor/dbimport/DbImportTree.java      |  60 ++++----
 .../editor/dbimport/PrintColumnsBiFunction.java    |  55 +++++++
 .../editor/dbimport/PrintTablesBiFunction.java     |  65 ++++++++
 9 files changed, 323 insertions(+), 135 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index b638050..2f57bc1 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -44,6 +44,7 @@ CAY-2593 Add tableTypes field to dbImport config in dataMap
 CAY-2602 Remove RTRIM of char columns in Sybase
 CAY-2610 Align methods in ObjectSelect and SQLSelect
 CAY-2611 Exclude system catalogs and schemas when run dbImport without config
+CAY-2612 Modeler: add lazy-loading to dbImport tab
 
 Bug Fixes:
 
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
index 5129df5..b1792f4 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
@@ -80,12 +80,10 @@ import org.apache.cayenne.resource.ResourceLocator;
 public class MySQLAdapter extends JdbcAdapter {
 
 	static final String DEFAULT_STORAGE_ENGINE = "InnoDB";
-	static final String MYSQL_QUOTE_SQL_IDENTIFIERS_CHAR_START = "`";
-	static final String MYSQL_QUOTE_SQL_IDENTIFIERS_CHAR_END = "`";
 
 	protected String storageEngine;
 
-	private String[] SYSTEM_CATALOGS = new String[]{"sys"};
+	private String[] SYSTEM_CATALOGS = new String[]{"sys", "information_schema", "mysql", "performance_schema"};
 
 	public MySQLAdapter(@Inject RuntimeProperties runtimeProperties,
 						@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
index b1b78a2..edf5e79 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
@@ -19,6 +19,16 @@
 
 package org.apache.cayenne.dba.postgres;
 
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
@@ -41,20 +51,11 @@ import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.resource.ResourceLocator;
 
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.sql.Types;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.function.Function;
-
 /**
  * DbAdapter implementation for <a href="http://www.postgresql.org">PostgreSQL
  * RDBMS </a>. Sample connection settings to use with PostgreSQL are shown
  * below:
- * 
+ *
  * <pre>
  *      postgres.jdbc.username = test
  *      postgres.jdbc.password = secret
@@ -66,6 +67,8 @@ public class PostgresAdapter extends JdbcAdapter {
 
 	public static final String BYTEA = "bytea";
 
+	private String[] SYSTEM_SCHEMAS = new String[]{"information_schema", "pg_catalog"};
+
 	public PostgresAdapter(@Inject RuntimeProperties runtimeProperties,
 						   @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
 						   @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
@@ -87,7 +90,7 @@ public class PostgresAdapter extends JdbcAdapter {
 
 	/**
 	 * Uses PostgresActionBuilder to create the right action.
-	 * 
+	 *
 	 * @since 1.2
 	 */
 	@Override
@@ -156,7 +159,7 @@ public class PostgresAdapter extends JdbcAdapter {
 	 * Customizes table creating procedure for PostgreSQL. One difference with
 	 * generic implementation is that "bytea" type has no explicit length unlike
 	 * similar binary types in other databases.
-	 * 
+	 *
 	 * @since 1.0.2
 	 */
 	@Override
@@ -267,4 +270,9 @@ public class PostgresAdapter extends JdbcAdapter {
 		return false;
 	}
 
+	@Override
+	public List<String> getSystemSchemas() {
+		return Arrays.asList(SYSTEM_SCHEMAS);
+	}
+
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
index c15eb64..0da1518 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
@@ -81,7 +81,10 @@ public class SQLServerAdapter extends SybaseAdapter {
 	@Deprecated
 	public static final String TRIM_FUNCTION = "RTRIM";
 
-	private String[] SYSTEM_SCHEMAS = new String[]{"dbo", "sys", "INFORMATION_SCHEMA"};
+	private String[] SYSTEM_SCHEMAS = new String[]{"db_accessadmin", "db_backupoperator",
+			"db_datareader", "db_datawriter", "db_ddladmin", "db_denydatareader",
+			"db_denydatawriter","dbo", "sys", "db_owner", "db_securityadmin", "guest",
+			"INFORMATION_SCHEMA"};
 
 	public SQLServerAdapter(@Inject RuntimeProperties runtimeProperties,
 							@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/LoadDbSchemaAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/LoadDbSchemaAction.java
index 3b6d773..871a502 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/LoadDbSchemaAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/LoadDbSchemaAction.java
@@ -22,15 +22,22 @@ package org.apache.cayenne.modeler.action;
 import javax.swing.JOptionPane;
 import javax.swing.tree.TreePath;
 import java.awt.event.ActionEvent;
+import java.sql.SQLException;
 import java.util.prefs.Preferences;
 
+import org.apache.cayenne.dbsync.reverse.dbimport.Catalog;
+import org.apache.cayenne.dbsync.reverse.dbimport.IncludeTable;
 import org.apache.cayenne.dbsync.reverse.dbimport.ReverseEngineering;
+import org.apache.cayenne.dbsync.reverse.dbimport.Schema;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.dialog.db.DataSourceWizard;
+import org.apache.cayenne.modeler.dialog.db.load.DbImportTreeNode;
 import org.apache.cayenne.modeler.editor.dbimport.DatabaseSchemaLoader;
 import org.apache.cayenne.modeler.editor.dbimport.DbImportModel;
 import org.apache.cayenne.modeler.editor.dbimport.DbImportView;
 import org.apache.cayenne.modeler.editor.dbimport.DraggableTreePanel;
+import org.apache.cayenne.modeler.editor.dbimport.PrintColumnsBiFunction;
+import org.apache.cayenne.modeler.editor.dbimport.PrintTablesBiFunction;
 import org.apache.cayenne.modeler.pref.DBConnectionInfo;
 import org.apache.cayenne.modeler.pref.DataMapDefaults;
 import org.apache.cayenne.modeler.util.CayenneAction;
@@ -86,24 +93,20 @@ public class LoadDbSchemaAction extends CayenneAction {
                 }
 
                 if (tablePath != null) {
-                    ReverseEngineering databaseReverseEngineering = new DatabaseSchemaLoader()
-                            .loadColumns(connectionInfo, getApplication().getClassLoadingService(), tablePath);
-                    draggableTreePanel.getSourceTree().updateTableColumns(databaseReverseEngineering);
+                    Object userObject = ((DbImportTreeNode) tablePath.getLastPathComponent()).getUserObject();
+                    if(userObject instanceof Catalog) {
+                        Catalog catalog = (Catalog) userObject;
+                        if(catalog.getSchemas().isEmpty()) {
+                            loadTables(connectionInfo, tablePath, rootParent);
+                        }
+                    } else if(userObject instanceof Schema) {
+                        loadTables(connectionInfo, tablePath, rootParent);
+                    } else if(userObject instanceof IncludeTable) {
+                        loadColumns(connectionInfo, tablePath);
+                    }
                 } else {
-                    ReverseEngineering databaseReverseEngineering = new DatabaseSchemaLoader()
-                            .load(connectionInfo,
-                                    getApplication().getClassLoadingService(),
-                                    rootParent.getTableTypes());
-                    draggableTreePanel.getSourceTree()
-                            .setEnabled(true);
-                    draggableTreePanel.getSourceTree()
-                            .translateReverseEngineeringToTree(databaseReverseEngineering, true);
-                    draggableTreePanel
-                            .bindReverseEngineeringToDatamap(getProjectController().getCurrentDataMap(), databaseReverseEngineering);
-                    ((DbImportModel) draggableTreePanel.getSourceTree().getModel()).reload();
+                    loadDataBase(connectionInfo);
                 }
-
-
             } catch (Exception exception) {
                 JOptionPane.showMessageDialog(
                         Application.getFrame(),
@@ -119,6 +122,40 @@ public class LoadDbSchemaAction extends CayenneAction {
         thread.start();
     }
 
+    private void loadDataBase(DBConnectionInfo connectionInfo) throws Exception {
+        ReverseEngineering databaseReverseEngineering = new DatabaseSchemaLoader()
+                .load(connectionInfo,
+                        getApplication().getClassLoadingService());
+        draggableTreePanel.getSourceTree()
+                .setEnabled(true);
+        draggableTreePanel.getSourceTree()
+                .translateReverseEngineeringToTree(databaseReverseEngineering, true);
+        draggableTreePanel
+                .bindReverseEngineeringToDatamap(getProjectController().getCurrentDataMap(), databaseReverseEngineering);
+        ((DbImportModel) draggableTreePanel.getSourceTree().getModel()).reload();
+    }
+
+    private void loadTables(DBConnectionInfo connectionInfo,
+                            TreePath tablePath,
+                            DbImportView rootParent) throws SQLException {
+        ReverseEngineering databaseReverseEngineering = new DatabaseSchemaLoader()
+                .loadTables(connectionInfo,
+                        getApplication().getClassLoadingService(),
+                        tablePath,
+                        rootParent.getTableTypes());
+        draggableTreePanel.getSourceTree()
+                .update(databaseReverseEngineering,
+                        new PrintTablesBiFunction(draggableTreePanel.getSourceTree()));
+    }
+
+    private void loadColumns(DBConnectionInfo connectionInfo, TreePath tablePath) throws SQLException {
+        ReverseEngineering databaseReverseEngineering = new DatabaseSchemaLoader()
+                .loadColumns(connectionInfo, getApplication().getClassLoadingService(), tablePath);
+        draggableTreePanel.getSourceTree()
+                .update(databaseReverseEngineering,
+                        new PrintColumnsBiFunction(draggableTreePanel.getSourceTree()));
+    }
+
     private boolean datamapPrefNotExist() {
         Preferences dataMapPreference = getProjectController().
                 getDataMapPreferences(getProjectController().getCurrentDataMap())
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DatabaseSchemaLoader.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DatabaseSchemaLoader.java
index 5f22e33..90166c3 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DatabaseSchemaLoader.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DatabaseSchemaLoader.java
@@ -50,20 +50,15 @@ public class DatabaseSchemaLoader {
     }
 
     public ReverseEngineering load(DBConnectionInfo connectionInfo,
-                                   ClassLoadingService loadingService,
-                                   String[] tableTypesFromConfig) throws Exception {
+                                   ClassLoadingService loadingService) throws Exception {
         DbAdapter dbAdapter = connectionInfo.makeAdapter(loadingService);
         try (Connection connection = connectionInfo.makeDataSource(loadingService).getConnection()) {
-            String[] types = tableTypesFromConfig != null && tableTypesFromConfig.length != 0 ?
-                    tableTypesFromConfig :
-                    new String[]{"TABLE", "VIEW", "SYSTEM TABLE",
-                            "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM"};
-            processCatalogs(connection, types, dbAdapter);
+            processCatalogs(connection, dbAdapter);
         }
         return databaseReverseEngineering;
     }
 
-    private void processCatalogs(Connection connection, String[] types, DbAdapter dbAdapter) throws SQLException {
+    private void processCatalogs(Connection connection, DbAdapter dbAdapter) throws SQLException {
         try (ResultSet rsCatalog = connection.getMetaData().getCatalogs()) {
             boolean hasCatalogs = false;
             List<String> systemCatalogs = dbAdapter.getSystemCatalogs();
@@ -71,17 +66,16 @@ public class DatabaseSchemaLoader {
                 hasCatalogs = true;
                 String catalog = rsCatalog.getString("TABLE_CAT");
                 if(!systemCatalogs.contains(catalog)) {
-                    processSchemas(connection, types, catalog, dbAdapter);
+                    processSchemas(connection, catalog, dbAdapter);
                 }
             }
             if(!hasCatalogs) {
-                processSchemas(connection, types, null, dbAdapter);
+                processSchemas(connection, null, dbAdapter);
             }
         }
     }
 
     private void processSchemas(Connection connection,
-                                String[] types,
                                 String catalog,
                                 DbAdapter dbAdapter) throws SQLException {
         DatabaseMetaData metaData = connection.getMetaData();
@@ -92,17 +86,58 @@ public class DatabaseSchemaLoader {
                 hasSchemas = true;
                 String schema = rsSchema.getString("TABLE_SCHEM");
                 if(!systemSchemas.contains(schema)) {
-                    ResultSet resultSet = metaData.getTables(catalog, schema, INCLUDE_ALL_PATTERN, types);
-                    packTable(resultSet);
-                    packProcedures(connection);
+                    packFilterContainer(catalog, schema);
                 }
             }
             if(!hasSchemas) {
-                ResultSet resultSet = metaData.getTables(catalog, null, INCLUDE_ALL_PATTERN, types);
-                packTable(resultSet);
+                packFilterContainer(catalog, null);
+            }
+        }
+    }
+
+    public ReverseEngineering loadTables(DBConnectionInfo connectionInfo,
+                                         ClassLoadingService loadingService,
+                                         TreePath path,
+                                         String[] tableTypesFromConfig) throws SQLException {
+        int pathIndex = 1;
+        String catalogName = null, schemaName = null;
+
+        Object userObject = getUserObject(path, pathIndex);
+        if(userObject instanceof Catalog) {
+            Catalog catalog = (Catalog) userObject;
+            catalogName = catalog.getName();
+            if(!catalog.getSchemas().isEmpty()) {
+                userObject = getUserObject(path, ++pathIndex);
+                if (userObject instanceof Schema) {
+                    schemaName = ((Schema) userObject).getName();
+                }
+            }
+        } else if(userObject instanceof Schema) {
+            schemaName = ((Schema) userObject).getName();
+        }
+
+        try (Connection connection = connectionInfo.makeDataSource(loadingService).getConnection()) {
+            String[] types = tableTypesFromConfig != null && tableTypesFromConfig.length != 0 ?
+                    tableTypesFromConfig :
+                    new String[]{"TABLE", "VIEW", "SYSTEM TABLE",
+                            "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM"};
+            try (ResultSet resultSet = connection.getMetaData()
+                    .getTables(catalogName, schemaName, INCLUDE_ALL_PATTERN, types)) {
+                boolean hasTables = false;
+                while (resultSet.next()) {
+                    hasTables = true;
+                    String table = resultSet.getString("TABLE_NAME");
+                    String schema = resultSet.getString("TABLE_SCHEM");
+                    String catalog = resultSet.getString("TABLE_CAT");
+                    packTable(table, catalog == null ? catalogName : catalog, schema, null);
+                }
+                if(!hasTables) {
+                    packFilterContainer(catalogName, schemaName);
+                }
                 packProcedures(connection);
             }
         }
+        return databaseReverseEngineering;
     }
 
     public ReverseEngineering loadColumns(DBConnectionInfo connectionInfo,
@@ -136,6 +171,48 @@ public class DatabaseSchemaLoader {
         return databaseReverseEngineering;
     }
 
+    private FilterContainer packFilterContainer(String catalogName, String schemaName) {
+        if (catalogName != null && schemaName == null) {
+            Catalog parentCatalog = getCatalogByName(databaseReverseEngineering.getCatalogs(), catalogName);
+
+            if(parentCatalog == null) {
+                parentCatalog = new Catalog();
+                parentCatalog.setName(catalogName);
+                databaseReverseEngineering.addCatalog(parentCatalog);
+            }
+
+            return parentCatalog;
+        } else if (catalogName == null) {
+            Schema parentSchema = getSchemaByName(databaseReverseEngineering.getSchemas(), schemaName);
+
+            if(parentSchema == null) {
+                parentSchema = new Schema();
+                parentSchema.setName(schemaName);
+                databaseReverseEngineering.addSchema(parentSchema);
+            }
+            return parentSchema;
+        } else {
+            Catalog parentCatalog = getCatalogByName(databaseReverseEngineering.getCatalogs(), catalogName);
+            Schema parentSchema;
+            if (parentCatalog != null) {
+                parentSchema = getSchemaByName(parentCatalog.getSchemas(), schemaName);
+                if(parentSchema == null) {
+                    parentSchema = new Schema();
+                    parentSchema.setName(schemaName);
+                    parentCatalog.addSchema(parentSchema);
+                }
+            } else {
+                parentCatalog = new Catalog();
+                parentCatalog.setName(catalogName);
+                parentSchema = new Schema();
+                parentSchema.setName(schemaName);
+                parentCatalog.addSchema(parentSchema);
+                databaseReverseEngineering.addCatalog(parentCatalog);
+            }
+            return parentSchema;
+        }
+    }
+
     private Object getUserObject(TreePath path, int pathIndex) {
         return ((DbImportTreeNode)path.getPathComponent(pathIndex)).getUserObject();
     }
@@ -154,18 +231,18 @@ public class DatabaseSchemaLoader {
             if(!schemas.isEmpty()) {
                 for(Schema schema : schemas) {
                     ResultSet procResultSet = getProcedures(connection, catalog.getName(), schema.getName());
-                    packFunction(procResultSet, schema);
+                    packProcedures(procResultSet, schema);
                 }
             } else {
                 ResultSet procResultSet = getProcedures(connection, catalog.getName(), null);
-                packFunction(procResultSet, catalog);
+                packProcedures(procResultSet, catalog);
             }
         }
 
         Collection<Schema> schemas = databaseReverseEngineering.getSchemas();
         for(Schema schema : schemas) {
             ResultSet procResultSet = getProcedures(connection, null, schema.getName());
-            packFunction(procResultSet, schema);
+            packProcedures(procResultSet, schema);
         }
     }
 
@@ -173,7 +250,7 @@ public class DatabaseSchemaLoader {
         return connection.getMetaData().getProcedures(catalog, schema, "%");
     }
 
-    private void packFunction(ResultSet resultSet, FilterContainer filterContainer) throws SQLException {
+    private void packProcedures(ResultSet resultSet, FilterContainer filterContainer) throws SQLException {
         while (resultSet.next()) {
             IncludeProcedure includeProcedure =
                     new IncludeProcedure(resultSet.getString("PROCEDURE_NAME"));
@@ -183,15 +260,6 @@ public class DatabaseSchemaLoader {
         }
     }
 
-    private void packTable(ResultSet resultSet) throws SQLException {
-        while (resultSet.next()) {
-            String tableName = resultSet.getString("TABLE_NAME");
-            String schemaName = resultSet.getString("TABLE_SCHEM");
-            String catalogName = resultSet.getString("TABLE_CAT");
-            packTable(tableName, catalogName, schemaName, null);
-        }
-    }
-
     private void packTable(String tableName, String catalogName, String schemaName, String columnName) {
         IncludeTable table = new IncludeTable();
         table.setPattern(tableName);
@@ -203,46 +271,7 @@ public class DatabaseSchemaLoader {
             return;
         }
 
-        FilterContainer filterContainer;
-        if (catalogName != null && schemaName == null) {
-            Catalog parentCatalog = getCatalogByName(databaseReverseEngineering.getCatalogs(), catalogName);
-
-            if(parentCatalog == null) {
-                parentCatalog = new Catalog();
-                parentCatalog.setName(catalogName);
-                databaseReverseEngineering.addCatalog(parentCatalog);
-            }
-            filterContainer = parentCatalog;
-        } else if (catalogName == null) {
-            Schema parentSchema = getSchemaByName(databaseReverseEngineering.getSchemas(), schemaName);
-
-            if(parentSchema == null) {
-                parentSchema = new Schema();
-                parentSchema.setName(schemaName);
-                databaseReverseEngineering.addSchema(parentSchema);
-            }
-            filterContainer = parentSchema;
-        } else {
-            Catalog parentCatalog = getCatalogByName(databaseReverseEngineering.getCatalogs(), catalogName);
-            Schema parentSchema;
-            if (parentCatalog != null) {
-                parentSchema = getSchemaByName(parentCatalog.getSchemas(), schemaName);
-                if(parentSchema == null) {
-                    parentSchema = new Schema();
-                    parentSchema.setName(schemaName);
-                    parentCatalog.addSchema(parentSchema);
-                }
-            } else {
-                parentCatalog = new Catalog();
-                parentCatalog.setName(catalogName);
-                parentSchema = new Schema();
-                parentSchema.setName(schemaName);
-                parentCatalog.addSchema(parentSchema);
-                databaseReverseEngineering.addCatalog(parentCatalog);
-            }
-            filterContainer = parentSchema;
-        }
-
+        FilterContainer filterContainer = packFilterContainer(catalogName, schemaName);
         addTable(filterContainer, table);
         addColumn(filterContainer, table, columnName);
     }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTree.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTree.java
index 86f49c7..f5e2053 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTree.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTree.java
@@ -27,6 +27,7 @@ import javax.swing.tree.TreePath;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.function.BiFunction;
 
 import org.apache.cayenne.dbsync.reverse.dbimport.Catalog;
 import org.apache.cayenne.dbsync.reverse.dbimport.ExcludeTable;
@@ -73,7 +74,8 @@ public class DbImportTree extends JTree {
         model.reload();
     }
 
-    public void updateTableColumns(ReverseEngineering reverseEngineering) {
+    public void update(ReverseEngineering reverseEngineering,
+                       BiFunction<FilterContainer, DbImportTreeNode, Void> processor) {
         DbImportModel model = (DbImportModel) this.getModel();
         DbImportTreeNode root = (DbImportTreeNode) model.getRoot();
         Collection<Catalog> catalogs = reverseEngineering.getCatalogs();
@@ -82,55 +84,34 @@ public class DbImportTree extends JTree {
                 Collection<Schema> schemas = catalog.getSchemas();
                 if(!schemas.isEmpty()) {
                     DbImportTreeNode currentRoot = findNodeInParent(root, catalog);
-                    schemas.forEach(schema -> packNextFilter(schema, currentRoot, model));
+                    schemas.forEach(schema -> packNextFilter(schema, currentRoot, processor));
                 } else {
-                    packNextFilter(catalog, root, model);
+                    packNextFilter(catalog, root, processor);
                 }
             });
         } else {
-            reverseEngineering.getSchemas().forEach(schema -> {
-                packNextFilter(schema, root, model);
-            });
+            reverseEngineering.getSchemas().forEach(schema -> packNextFilter(schema, root, processor));
         }
     }
 
-    private void packNextFilter(FilterContainer filterContainer,
-                                DbImportTreeNode root,
-                                DbImportModel model) {
+    private void packNextFilter(FilterContainer filterContainer, DbImportTreeNode root,
+                                BiFunction<FilterContainer, DbImportTreeNode, Void> processor) {
         DbImportTreeNode container = findNodeInParent(root, filterContainer);
 
         if (container == null) {
             return;
         }
 
-        packTables(filterContainer, container, model);
-    }
-
-    private void packTables(FilterContainer filterContainer,
-                            DbImportTreeNode root,
-                            DbImportModel model) {
-        filterContainer.getIncludeTables().forEach(tableFilter -> {
-            DbImportTreeNode container = findNodeInParent(root, tableFilter );
-            if (container == null) {
-                return;
-            }
-            if (container.getChildCount() != 0) {
-                container.removeAllChildren();
-            }
-
-            packColumns(tableFilter , container);
-
-            container.setLoaded(true);
-            model.reload(container);
-        });
+        container.setLoaded(true);
+        processor.apply(filterContainer, container);
     }
 
-    private void packColumns(IncludeTable includeTable, DbImportTreeNode tableNode) {
+    void packColumns(IncludeTable includeTable, DbImportTreeNode tableNode) {
         includeTable.getIncludeColumns().forEach(column ->
                 tableNode.add(new DbImportTreeNode(column)));
     }
 
-    private DbImportTreeNode findNodeInParent(DbImportTreeNode parent, Object object) {
+    DbImportTreeNode findNodeInParent(DbImportTreeNode parent, Object object) {
         for (int i = 0; i < parent.getChildCount(); i++) {
             DbImportTreeNode node = (DbImportTreeNode) parent.getChildAt(i);
             Object userObject = node.getUserObject();
@@ -258,7 +239,7 @@ public class DbImportTree extends JTree {
         expandBeginningWithNode(getRootNode(), expandIndexesList);
     }
 
-    private <T extends PatternParam> void printParams(Collection<T> collection, DbImportTreeNode parent) {
+    public  <T extends PatternParam> void printParams(Collection<T> collection, DbImportTreeNode parent) {
         for (T element : collection) {
             DbImportTreeNode node = !isTransferable ? new DbImportTreeNode(element) : new TransferableNode(element);
             if (!node.getSimpleNodeName().equals("")) {
@@ -296,6 +277,11 @@ public class DbImportTree extends JTree {
         for (Schema schema : schemas) {
             DbImportTreeNode node = !isTransferable ? new DbImportTreeNode(schema) : new TransferableNode(schema);
             if (!node.getSimpleNodeName().equals("")) {
+
+                if (isTransferable && schema.getIncludeTables().isEmpty() && schema.getExcludeTables().isEmpty()) {
+                    printParams(Collections.singletonList(new IncludeTable("Loading...")), node);
+                }
+
                 printChildren(schema, node);
                 parent.add(node);
             }
@@ -306,6 +292,12 @@ public class DbImportTree extends JTree {
         for (Catalog catalog : catalogs) {
             DbImportTreeNode node = !isTransferable ? new DbImportTreeNode(catalog) : new TransferableNode(catalog);
             if (!node.getSimpleNodeName().equals("")) {
+
+                if (isTransferable && catalog.getSchemas().isEmpty() &&
+                        catalog.getIncludeTables().isEmpty() && catalog.getExcludeTables().isEmpty()) {
+                    printParams(Collections.singletonList(new IncludeTable("Loading...")), node);
+                }
+
                 printSchemas(catalog.getSchemas(), node);
                 printChildren(catalog, node);
                 parent.add(node);
@@ -325,8 +317,8 @@ public class DbImportTree extends JTree {
                 }
 
                 DbImportTreeNode node = (DbImportTreeNode) lastPathComponent;
-                if (node.isIncludeTable() && !node.isLoaded()) {
-                    //reload columns action.
+                if ((node.isIncludeTable() || node.isSchema() || node.isCatalog()) && !node.isLoaded()) {
+                    //reload tables and columns action.
 
                     LoadDbSchemaAction action = Application.getInstance().getActionManager().getAction(LoadDbSchemaAction.class);
                     action.performAction(null, path);
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintColumnsBiFunction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintColumnsBiFunction.java
new file mode 100644
index 0000000..b7003fe
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintColumnsBiFunction.java
@@ -0,0 +1,55 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.modeler.editor.dbimport;
+
+import java.util.function.BiFunction;
+
+import org.apache.cayenne.dbsync.reverse.dbimport.FilterContainer;
+import org.apache.cayenne.modeler.dialog.db.load.DbImportTreeNode;
+
+public class PrintColumnsBiFunction implements BiFunction<FilterContainer, DbImportTreeNode, Void> {
+
+    private DbImportTree dbImportTree;
+
+    public PrintColumnsBiFunction(DbImportTree dbImportTree) {
+        this.dbImportTree = dbImportTree;
+    }
+
+    @Override
+    public Void apply(FilterContainer filterContainer, DbImportTreeNode root) {
+        DbImportModel model = (DbImportModel) dbImportTree.getModel();
+        filterContainer.getIncludeTables().forEach(tableFilter -> {
+            DbImportTreeNode container = dbImportTree
+                    .findNodeInParent(root, tableFilter);
+            if (container == null) {
+                return;
+            }
+            if (container.getChildCount() != 0) {
+                container.removeAllChildren();
+            }
+
+            dbImportTree.packColumns(tableFilter , container);
+
+            container.setLoaded(true);
+            model.reload(container);
+        });
+        return null;
+    }
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintTablesBiFunction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintTablesBiFunction.java
new file mode 100644
index 0000000..b547fb9
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintTablesBiFunction.java
@@ -0,0 +1,65 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.modeler.editor.dbimport;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.function.BiFunction;
+
+import org.apache.cayenne.dbsync.reverse.dbimport.FilterContainer;
+import org.apache.cayenne.dbsync.reverse.dbimport.IncludeColumn;
+import org.apache.cayenne.dbsync.reverse.dbimport.IncludeTable;
+import org.apache.cayenne.modeler.dialog.db.load.DbImportTreeNode;
+import org.apache.cayenne.modeler.dialog.db.load.TransferableNode;
+
+public class PrintTablesBiFunction implements BiFunction<FilterContainer, DbImportTreeNode, Void> {
+
+    private DbImportTree dbImportTree;
+
+    public PrintTablesBiFunction(DbImportTree dbImportTree) {
+        this.dbImportTree = dbImportTree;
+    }
+
+    @Override
+    public Void apply(FilterContainer filterContainer, DbImportTreeNode root) {
+        DbImportModel model = (DbImportModel) dbImportTree.getModel();
+        boolean isTransferable = dbImportTree.isTransferable();
+        if (root.getChildCount() != 0) {
+            root.removeAllChildren();
+        }
+        Collection<IncludeTable> includeTables = filterContainer.getIncludeTables();
+        for (IncludeTable includeTable : includeTables) {
+            DbImportTreeNode node = !isTransferable ?
+                    new DbImportTreeNode(includeTable) :
+                    new TransferableNode(includeTable);
+            if (isTransferable &&
+                    includeTable.getIncludeColumns().isEmpty() &&
+                    includeTable.getExcludeColumns().isEmpty()) {
+                dbImportTree.printParams(Collections.singletonList(
+                        new IncludeColumn("Loading...")), node);
+            }
+            root.add(node);
+            dbImportTree.packColumns(includeTable, node);
+        }
+        model.reload(root);
+
+        return null;
+    }
+}