/**
   * Retrieves a list of schemas from the database, for the table specified.
   *
   * @param tableSort TODO
   * @param table Catalog for which data is required.
   * @throws SQLException On a SQL exception
   */
  void retrieveSchemas(final InclusionRule schemaInclusionRule) throws SQLException {
    final MetadataResultSet results =
        new MetadataResultSet(getRetrieverConnection().getMetaData().getSchemas());
    try {
      final boolean supportsCatalogs = getRetrieverConnection().isSupportsCatalogs();
      while (results.next()) {
        final String catalogName;
        if (supportsCatalogs) {
          catalogName = results.getString("TABLE_CATALOG");
        } else {
          catalogName = null;
        }
        final String schemaName = results.getString("TABLE_SCHEM");
        LOGGER.log(Level.FINER, String.format("Retrieving schema: %s.%s", catalogName, schemaName));

        final MutableCatalog[] catalogs;
        final MutableCatalog catalog = database.getCatalog(catalogName);
        if (catalog != null) {
          catalogs = new MutableCatalog[] {catalog};
        } else {
          final Catalog[] databaseCatalogs = database.getCatalogs();
          catalogs = new MutableCatalog[databaseCatalogs.length];
          for (int i = 0; i < databaseCatalogs.length; i++) {
            catalogs[i] = (MutableCatalog) databaseCatalogs[i];
          }
        }

        for (final MutableCatalog currentCatalog : catalogs) {
          final MutableSchema schema = new MutableSchema(currentCatalog, schemaName);
          final String schemaFullName = schema.getFullName();
          if (schemaInclusionRule.include(schemaFullName)) {
            currentCatalog.addSchema(schema);
          }
        }
      }
    } finally {
      results.close();
    }

    for (final Catalog catalog : database.getCatalogs()) {
      if (catalog.getSchemas().length == 0) {
        final MutableCatalog mutableCatalog = (MutableCatalog) catalog;
        final MutableSchema schema = new MutableSchema(mutableCatalog, null);
        mutableCatalog.addSchema(schema);
      }
    }
  }
  private static void crawlTables(
      final MutableCatalog catalog,
      final RetrieverConnection retrieverConnection,
      final SchemaCrawlerOptions options)
      throws SchemaCrawlerException {

    final SchemaInfoLevel infoLevel = options.getSchemaInfoLevel();
    final boolean retrieveTables = infoLevel.isRetrieveTables();
    if (!retrieveTables) {
      LOGGER.log(Level.INFO, "Not retrieving tables, since this was not requested");
      return;
    }

    final StopWatch stopWatch = new StopWatch("crawlTables");

    LOGGER.log(Level.INFO, "Crawling tables");

    final TableRetriever retriever;
    final TableColumnRetriever columnRetriever;
    final ForeignKeyRetriever fkRetriever;
    final TableExtRetriever retrieverExtra;
    try {
      retriever = new TableRetriever(retrieverConnection, catalog);
      columnRetriever = new TableColumnRetriever(retrieverConnection, catalog);
      fkRetriever = new ForeignKeyRetriever(retrieverConnection, catalog);
      retrieverExtra = new TableExtRetriever(retrieverConnection, catalog);

      stopWatch.time(
          "retrieveTables",
          () -> {
            for (final Schema schema : retriever.getSchemas()) {
              retriever.retrieveTables(
                  schema.getCatalogName(),
                  schema.getName(),
                  options.getTableNamePattern(),
                  options.getTableTypes(),
                  options.getTableInclusionRule());
            }
            return null;
          });

      final NamedObjectList<MutableTable> allTables = catalog.getAllTables();

      stopWatch.time(
          "retrieveColumns",
          () -> {
            if (infoLevel.isRetrieveTableColumns()) {
              columnRetriever.retrieveColumns(allTables, options.getColumnInclusionRule());
            }
            return null;
          });

      stopWatch.time(
          "retrieveForeignKeys",
          () -> {
            if (infoLevel.isRetrieveForeignKeys()) {
              if (infoLevel.isRetrieveTableColumns()) {
                fkRetriever.retrieveForeignKeys(allTables);
              }
            } else {
              LOGGER.log(
                  Level.WARNING,
                  "Foreign-keys are not being retrieved, so tables cannot be sorted using the natural sort order");
            }
            return null;
          });

      stopWatch.time(
          "filterAndSortTables",
          () -> {
            // Filter the list of tables based on grep criteria, and
            // parent-child relationships
            final Predicate<Table> tableFilter = tableFilter(options);
            ((Reducible) catalog).reduce(Table.class, new TablesReducer(options, tableFilter));

            // Sort the remaining tables
            final TablesGraph tablesGraph = new TablesGraph(allTables);
            tablesGraph.setTablesSortIndexes();

            return null;
          });

      stopWatch.time(
          "retrieveIndexes",
          () -> {
            LOGGER.log(Level.INFO, "Retrieving primary keys and indexes");
            for (final MutableTable table : allTables) {
              final boolean isView = table instanceof MutableView;
              if (!isView && infoLevel.isRetrieveTableColumns()) {
                retriever.retrievePrimaryKey(table);
                if (infoLevel.isRetrieveIndexes()) {
                  retriever.retrieveIndexes(table, true);
                  retriever.retrieveIndexes(table, false);
                  //
                  table.replacePrimaryKey();
                }
              }
            }
            return null;
          });

      stopWatch.time(
          "retrieveTableConstraintInformation",
          () -> {
            if (infoLevel.isRetrieveTableConstraintInformation()) {
              retrieverExtra.retrieveTableConstraintInformation();
            }
            return null;
          });
      stopWatch.time(
          "retrieveTriggerInformation",
          () -> {
            if (infoLevel.isRetrieveTriggerInformation()) {
              retrieverExtra.retrieveTriggerInformation();
            }
            return null;
          });
      stopWatch.time(
          "retrieveViewInformation",
          () -> {
            if (infoLevel.isRetrieveViewInformation()) {
              retrieverExtra.retrieveViewInformation();
            }
            return null;
          });
      stopWatch.time(
          "retrieveTableDefinitions",
          () -> {
            if (infoLevel.isRetrieveTableDefinitionsInformation()) {
              retrieverExtra.retrieveTableDefinitions();
            }
            return null;
          });
      stopWatch.time(
          "retrieveIndexInformation",
          () -> {
            if (infoLevel.isRetrieveIndexInformation()) {
              retrieverExtra.retrieveIndexInformation();
            }
            return null;
          });

      stopWatch.time(
          "retrieveAdditionalTableAttributes",
          () -> {
            if (infoLevel.isRetrieveAdditionalTableAttributes()) {
              retrieverExtra.retrieveAdditionalTableAttributes();
            }
            return null;
          });
      stopWatch.time(
          "retrieveTablePrivileges",
          () -> {
            if (infoLevel.isRetrieveTablePrivileges()) {
              retrieverExtra.retrieveTablePrivileges();
            }
            return null;
          });

      stopWatch.time(
          "retrieveAdditionalColumnAttributes",
          () -> {
            if (infoLevel.isRetrieveAdditionalColumnAttributes()) {
              retrieverExtra.retrieveAdditionalColumnAttributes();
            }
            return null;
          });
      stopWatch.time(
          "retrieveTableColumnPrivileges",
          () -> {
            if (infoLevel.isRetrieveTableColumnPrivileges()) {
              retrieverExtra.retrieveTableColumnPrivileges();
            }
            return null;
          });

      LOGGER.log(Level.INFO, stopWatch.toString());
    } catch (final Exception e) {
      if (e instanceof SchemaCrawlerSQLException) {
        throw new SchemaCrawlerException(e.getMessage(), e.getCause());
      } else if (e instanceof SchemaCrawlerException) {
        throw (SchemaCrawlerException) e;
      } else {
        throw new SchemaCrawlerException("Exception retrieving table information", e);
      }
    }
  }
  private static void crawlRoutines(
      final MutableCatalog catalog,
      final RetrieverConnection retrieverConnection,
      final SchemaCrawlerOptions options)
      throws SchemaCrawlerException {
    final StopWatch stopWatch = new StopWatch("crawlRoutines");

    final SchemaInfoLevel infoLevel = options.getSchemaInfoLevel();
    final boolean retrieveRoutines = infoLevel.isRetrieveRoutines();
    if (!retrieveRoutines) {
      LOGGER.log(Level.INFO, "Not retrieving routines, since this was not requested");
      return;
    }

    LOGGER.log(Level.INFO, "Crawling routines");

    final RoutineRetriever retriever;
    final RoutineExtRetriever retrieverExtra;
    try {
      retriever = new RoutineRetriever(retrieverConnection, catalog);
      retrieverExtra = new RoutineExtRetriever(retrieverConnection, catalog);
      final Collection<RoutineType> routineTypes = options.getRoutineTypes();

      stopWatch.time(
          "retrieveRoutines",
          () -> {
            for (final Schema schema : retriever.getSchemas()) {
              if (routineTypes.contains(RoutineType.procedure)) {
                retriever.retrieveProcedures(
                    schema.getCatalogName(), schema.getName(), options.getRoutineInclusionRule());
              }
              if (routineTypes.contains(RoutineType.function)) {
                retriever.retrieveFunctions(
                    schema.getCatalogName(), schema.getName(), options.getRoutineInclusionRule());
              }
            }
            return null;
          });

      final NamedObjectList<MutableRoutine> allRoutines = catalog.getAllRoutines();

      stopWatch.time(
          "retrieveRoutineColumns",
          () -> {
            LOGGER.log(Level.INFO, "Retrieving routine columns");
            for (final MutableRoutine routine : allRoutines) {
              if (infoLevel.isRetrieveRoutineColumns()) {
                if (routine instanceof MutableProcedure
                    && routineTypes.contains(RoutineType.procedure)) {
                  retriever.retrieveProcedureColumns(
                      (MutableProcedure) routine, options.getRoutineColumnInclusionRule());
                }

                if (routine instanceof MutableFunction
                    && routineTypes.contains(RoutineType.function)) {
                  retriever.retrieveFunctionColumns(
                      (MutableFunction) routine, options.getRoutineColumnInclusionRule());
                }
              }
            }
            return null;
          });

      stopWatch.time(
          "filterRoutines",
          () -> {
            // Filter the list of routines based on grep criteria
            final Predicate<Routine> routineFilter = routineFilter(options);
            ((Reducible) catalog).reduce(Routine.class, new RoutinesReducer(routineFilter));
            return null;
          });

      stopWatch.time(
          "retrieveRoutineInformation",
          () -> {
            if (infoLevel.isRetrieveRoutineInformation()) {
              retrieverExtra.retrieveRoutineInformation();
            }
            return null;
          });

      LOGGER.log(Level.INFO, stopWatch.toString());
    } catch (final Exception e) {
      if (e instanceof SchemaCrawlerSQLException) {
        throw new SchemaCrawlerException(e.getMessage(), e.getCause());
      } else if (e instanceof SchemaCrawlerException) {
        throw (SchemaCrawlerException) e;
      } else {
        throw new SchemaCrawlerException("Exception retrieving routine information", e);
      }
    }
  }