@Test
  public void synonyms() throws Exception {
    final String[] classes = {
      "MutableTable", "MutableTable", "MutableTable", "", "", "",
    };
    final String[] synonymNames = {
      "AUTHORS", "BOOKAUTHORS", "BOOKS", "\"Global Counts\"", "No_Columns", "PUBLICATIONS",
    };

    final InformationSchemaViews informationSchemaViews = new InformationSchemaViews();
    informationSchemaViews.setSynonymSql(
        "SELECT LIMIT 1 3                                  \n"
            + "  TABLE_CATALOG AS SYNONYM_CATALOG,             \n"
            + "  TABLE_SCHEMA AS SYNONYM_SCHEMA,               \n"
            + "  TABLE_NAME AS SYNONYM_NAME,                   \n"
            + "  TABLE_CATALOG AS REFERENCED_OBJECT_CATALOG,   \n"
            + "  TABLE_SCHEMA AS REFERENCED_OBJECT_SCHEMA,     \n"
            + "  TABLE_NAME AS REFERENCED_OBJECT_NAME          \n"
            + "FROM                                            \n"
            + "  INFORMATION_SCHEMA.TABLES                     \n"
            + "WHERE                                           \n"
            + "  TABLE_SCHEMA = 'BOOKS'                        \n"
            + "UNION                                           \n"
            + "SELECT LIMIT 1 3                                \n"
            + "  'PUBLIC' AS SYNONYM_CATALOG,                  \n"
            + "  'BOOKS' AS SYNONYM_SCHEMA,                    \n"
            + "  TABLE_NAME AS SYNONYM_NAME,                   \n"
            + "  TABLE_CATALOG AS REFERENCED_OBJECT_CATALOG,   \n"
            + "  TABLE_SCHEMA AS REFERENCED_OBJECT_SCHEMA,     \n"
            + "  TABLE_NAME + '1' AS REFERENCED_OBJECT_NAME    \n"
            + "FROM                                            \n"
            + "  INFORMATION_SCHEMA.TABLES                     \n"
            + "WHERE                                           \n"
            + "  TABLE_SCHEMA != 'BOOKS'                       ");

    final SchemaInfoLevel minimum = SchemaInfoLevel.minimum();
    minimum.setRetrieveSynonymInformation(true);

    final SchemaCrawlerOptions schemaCrawlerOptions = new SchemaCrawlerOptions();
    schemaCrawlerOptions.setSchemaInfoLevel(minimum);
    schemaCrawlerOptions.setInformationSchemaViews(informationSchemaViews);
    schemaCrawlerOptions.setSynonymInclusionRule(InclusionRule.INCLUDE_ALL);

    final Database database = getDatabase(schemaCrawlerOptions);
    final Schema schema = database.getSchema("PUBLIC.BOOKS");
    assertNotNull("BOOKS Schema not found", schema);
    final Synonym[] synonyms = database.getSynonyms(schema).toArray(new Synonym[0]);
    assertEquals("Synonym count does not match", 6, synonyms.length);
    for (int i = 0; i < synonyms.length; i++) {
      final Synonym synonym = synonyms[i];
      assertNotNull(synonym);
      assertEquals(
          "Wrong referenced object class - " + synonym.getReferencedObject().getClass(),
          classes[i],
          synonym.getReferencedObject().getClass().getSimpleName());
      assertEquals("", synonymNames[i], synonym.getName());
    }
  }
  private static void crawlColumnDataTypes(
      final MutableCatalog catalog,
      final RetrieverConnection retrieverConnection,
      final SchemaCrawlerOptions options)
      throws SchemaCrawlerException {
    try {
      LOGGER.log(Level.INFO, "Crawling column data types");

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

      final SchemaInfoLevel infoLevel = options.getSchemaInfoLevel();
      final DatabaseInfoRetriever retriever =
          new DatabaseInfoRetriever(retrieverConnection, catalog);

      stopWatch.time(
          "retrieveSystemColumnDataTypes",
          () -> {
            if (infoLevel.isRetrieveColumnDataTypes()) {
              LOGGER.log(Level.INFO, "Retrieving system column data types");
              retriever.retrieveSystemColumnDataTypes();
            } else {
              LOGGER.log(
                  Level.INFO,
                  "Not retrieving system column data types, since this was not requested");
            }
            return null;
          });

      stopWatch.time(
          "retrieveUserDefinedColumnDataTypes",
          () -> {
            if (infoLevel.isRetrieveUserDefinedColumnDataTypes()) {
              LOGGER.log(Level.INFO, "Retrieving user column data types");
              for (final Schema schema : retriever.getSchemas()) {
                retriever.retrieveUserDefinedColumnDataTypes(
                    schema.getCatalogName(), schema.getName());
              }
            } else {
              LOGGER.log(
                  Level.INFO,
                  "Not retrieving user column data types, since this was not requested");
            }
            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 column data type information", e);
      }
    }
  }
  @Test
  public void schemaCounts() throws Exception {
    final String[] dataSources = {
      "MicrosoftSQLServer", "MySQL", "Oracle", "PostgreSQL", "SQLite",
    };
    final int[] catalogCounts = {
      4, 4, 1, 1, 1,
    };
    final int[][] schemaCounts = {
      {
        5, 5, 5, 5,
      },
      {
        1, 1, 1, 1,
      },
      {
        14,
      },
      {
        5,
      },
      {
        1,
      },
    };

    final SchemaCrawlerOptions schemaCrawlerOptions = createOptions(".*", ".*");
    final SchemaInfoLevel infoLevel = SchemaInfoLevel.minimum();
    infoLevel.setRetrieveTables(false);
    infoLevel.setRetrieveProcedures(false);
    schemaCrawlerOptions.setSchemaInfoLevel(infoLevel);

    for (int i = 0; i < dataSources.length; i++) {
      final String dataSource = dataSources[i];
      final Database database = retrieveDatabase(dataSource, schemaCrawlerOptions);
      final Catalog[] catalogs = database.getCatalogs();
      assertEquals(
          "Incorrect number of catalogs for " + dataSource, catalogCounts[i], catalogs.length);
      for (int j = 0; j < catalogs.length; j++) {
        final Catalog catalog = catalogs[j];
        final Schema[] schemas = catalog.getSchemas();
        assertEquals(
            "Incorrect number of schemas for " + dataSource + " catalog #" + j,
            schemaCounts[i][j],
            schemas.length);
      }
    }
  }
  @Test
  public void triggers() throws Exception {

    // Set up information schema properties
    final InformationSchemaViews informationSchemaViews = new InformationSchemaViews();
    informationSchemaViews.setTriggersSql("SELECT * FROM INFORMATION_SCHEMA.TRIGGERS");

    final SchemaCrawlerOptions schemaCrawlerOptions = new SchemaCrawlerOptions();
    schemaCrawlerOptions.setInformationSchemaViews(informationSchemaViews);
    schemaCrawlerOptions.setSchemaInfoLevel(SchemaInfoLevel.maximum());
    final Database database = getDatabase(schemaCrawlerOptions);
    final Schema schema = new SchemaReference("PUBLIC", "BOOKS");
    final Table[] tables = database.getTables(schema).toArray(new Table[0]);
    boolean foundTrigger = false;
    for (final Table table : tables) {
      for (final Trigger trigger : table.getTriggers()) {
        foundTrigger = true;
        assertEquals(
            "Triggers full name does not match",
            "PUBLIC.BOOKS.AUTHORS.TRG_AUTHORS",
            trigger.getFullName());
        assertEquals(
            "Trigger EventManipulationType does not match",
            EventManipulationType.delete,
            trigger.getEventManipulationType());
      }
    }
    assertTrue("No triggers found", foundTrigger);
  }
  public static void main(final String[] args) throws Exception {
    // Create a database connection
    final DataSource dataSource =
        new DatabaseConnectionOptions(
            "org.hsqldb.jdbcDriver", "jdbc:hsqldb:hsql://localhost:9001/schemacrawler");
    final Connection connection = dataSource.getConnection("sa", "");

    // Create the options
    final SchemaCrawlerOptions options = new SchemaCrawlerOptions();
    // Set what details are required in the schema - this affects the
    // time taken to crawl the schema
    options.setSchemaInfoLevel(SchemaInfoLevel.standard());
    options.setRoutineInclusionRule(new InclusionRule(InclusionRule.NONE, InclusionRule.ALL));
    options.setSchemaInclusionRule(new InclusionRule("PUBLIC.BOOKS", InclusionRule.NONE));

    // Get the schema definition
    final Database database = SchemaCrawlerUtility.getDatabase(connection, options);

    for (final Schema schema : database.getSchemas()) {
      System.out.println(schema);
      for (final Table table : database.getTables(schema)) {
        System.out.print("o--> " + table);
        if (table instanceof View) {
          System.out.println(" (VIEW)");
        } else {
          System.out.println();
        }

        for (final Column column : table.getColumns()) {
          System.out.println("     o--> " + column + " (" + column.getColumnDataType() + ")");
        }
      }
    }
  }
  private static void crawlSequences(
      final MutableCatalog catalog,
      final RetrieverConnection retrieverConnection,
      final SchemaCrawlerOptions options)
      throws SchemaCrawlerException {
    final StopWatch stopWatch = new StopWatch("crawlSequences");

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

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

    final SequenceRetriever retrieverExtra;
    try {
      retrieverExtra = new SequenceRetriever(retrieverConnection, catalog);

      stopWatch.time(
          "retrieveSequenceInformation",
          () -> {
            retrieverExtra.retrieveSequenceInformation(options.getSequenceInclusionRule());
            return null;
          });

      stopWatch.time(
          "sortAndFilterSequences",
          () -> {
            ((Reducible) catalog).reduce(Sequence.class, new SequencesReducer(options));
            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 sequence information", e);
      }
    }
  }
  @Test
  public void tables() throws Exception {

    final String referenceFile = "tables.txt";
    final File testOutputFile =
        File.createTempFile("schemacrawler." + referenceFile + ".", ".test");
    testOutputFile.delete();

    final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(testOutputFile)));

    final Config config = Config.loadResource("/hsqldb.INFORMATION_SCHEMA.config.properties");
    final SchemaCrawlerOptions schemaCrawlerOptions = new SchemaCrawlerOptions(config);
    schemaCrawlerOptions.setSchemaInfoLevel(SchemaInfoLevel.maximum());
    schemaCrawlerOptions.setSchemaInclusionRule(
        new InclusionRule(InclusionRule.ALL, ".*\\.FOR_LINT"));

    final Database database = getDatabase(schemaCrawlerOptions);
    final Schema[] schemas = database.getSchemas().toArray(new Schema[0]);
    assertEquals("Schema count does not match", 5, schemas.length);
    for (final Schema schema : schemas) {
      final Table[] tables = database.getTables(schema).toArray(new Table[0]);
      Arrays.sort(tables, NamedObjectSort.alphabetical);
      for (final Table table : tables) {
        writer.println(String.format("o--> %s [%s]", table.getFullName(), table.getTableType()));
        final SortedMap<String, Object> tableAttributes =
            new TreeMap<String, Object>(table.getAttributes());
        for (final Entry<String, Object> tableAttribute : tableAttributes.entrySet()) {
          writer.println(
              String.format("      ~ %s=%s", tableAttribute.getKey(), tableAttribute.getValue()));
        }
        final Column[] columns = table.getColumns().toArray(new Column[0]);
        for (final Column column : columns) {
          writer.println(
              String.format("   o--> %s [%s]", column.getFullName(), column.getColumnDataType()));
          final SortedMap<String, Object> columnAttributes =
              new TreeMap<String, Object>(column.getAttributes());
          for (final Entry<String, Object> columnAttribute : columnAttributes.entrySet()) {
            writer.println(
                String.format(
                    "          ~ %s=%s", columnAttribute.getKey(), columnAttribute.getValue()));
          }
        }
      }
    }

    writer.flush();
    writer.close();

    final List<String> failures =
        TestUtility.compareOutput(METADATA_OUTPUT + referenceFile, testOutputFile);
    if (failures.size() > 0) {
      fail(failures.toString());
    }
  }
 private SchemaCrawlerOptions createOptions(
     final String catalogInclusion, final String schemaInclusion) {
   final SchemaCrawlerOptions schemaCrawlerOptions = new SchemaCrawlerOptions();
   schemaCrawlerOptions.setSchemaInfoLevel(SchemaInfoLevel.maximum());
   if (catalogInclusion != null) {
     schemaCrawlerOptions.setCatalogInclusionRule(
         new InclusionRule(catalogInclusion, InclusionRule.NONE));
   }
   if (schemaInclusion != null) {
     schemaCrawlerOptions.setSchemaInclusionRule(
         new InclusionRule(schemaInclusion, InclusionRule.NONE));
   }
   return schemaCrawlerOptions;
 }
  @Test
  public void viewDefinitions() throws Exception {
    final InformationSchemaViews informationSchemaViews = new InformationSchemaViews();
    informationSchemaViews.setViewsSql("SELECT * FROM INFORMATION_SCHEMA.VIEWS");

    final SchemaCrawlerOptions schemaCrawlerOptions = new SchemaCrawlerOptions();
    schemaCrawlerOptions.setTableTypes("VIEW");
    schemaCrawlerOptions.setInformationSchemaViews(informationSchemaViews);
    schemaCrawlerOptions.setSchemaInfoLevel(SchemaInfoLevel.maximum());

    final Database database = getDatabase(schemaCrawlerOptions);
    final Schema schema = new SchemaReference("PUBLIC", "BOOKS");
    final View view = (View) database.getTable(schema, "AUTHORSLIST");
    assertNotNull("View not found", view);
    assertNotNull("View definition not found", view.getDefinition());
    assertFalse("View definition not found", view.getDefinition().trim().equals(""));
  }
  @Test
  public void routineDefinitions() throws Exception {

    final InformationSchemaViews informationSchemaViews = new InformationSchemaViews();
    informationSchemaViews.setRoutinesSql("SELECT * FROM INFORMATION_SCHEMA.ROUTINES");

    final SchemaCrawlerOptions schemaCrawlerOptions = new SchemaCrawlerOptions();
    schemaCrawlerOptions.setInformationSchemaViews(informationSchemaViews);
    schemaCrawlerOptions.setSchemaInfoLevel(SchemaInfoLevel.maximum());

    final Database database = getDatabase(schemaCrawlerOptions);
    final Schema schema = new SchemaReference("PUBLIC", "BOOKS");
    final Routine[] routines = database.getRoutines(schema).toArray(new Routine[0]);
    assertEquals("Wrong number of routines", 4, routines.length);
    for (final Routine routine : routines) {
      assertFalse(
          "Routine definition not found, for " + routine, Utility.isBlank(routine.getDefinition()));
    }
  }
  @Test
  public void schemaEquals() throws Exception {

    final SchemaCrawlerOptions schemaCrawlerOptions = new SchemaCrawlerOptions();
    schemaCrawlerOptions.setSchemaInfoLevel(SchemaInfoLevel.detailed());
    final Database database = getDatabase(schemaCrawlerOptions);
    final Schema schema1 = new SchemaReference("PUBLIC", "BOOKS");
    assertTrue("Could not find any tables", database.getTables(schema1).size() > 0);
    assertEquals("Wrong number of routines", 4, database.getRoutines(schema1).size());

    final Schema schema2 = new SchemaReference("PUBLIC", "BOOKS");

    assertEquals("Schema not not match", schema1, schema2);
    assertEquals("Tables do not match", database.getTables(schema1), database.getTables(schema2));
    assertEquals(
        "Routines do not match", database.getRoutines(schema1), database.getRoutines(schema2));

    // Try negative test
    final Table table1 = database.getTables(schema1).toArray(new Table[0])[0];
    final Table table2 = database.getTables(schema1).toArray(new Table[0])[1];
    assertFalse("Tables should not be equal", table1.equals(table2));
  }
  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);
      }
    }
  }
  private static void crawlDatabaseInfo(
      final MutableCatalog catalog,
      final RetrieverConnection retrieverConnection,
      final SchemaCrawlerOptions options)
      throws SchemaCrawlerException {
    try {
      final StopWatch stopWatch = new StopWatch("crawlDatabaseInfo");

      final SchemaInfoLevel infoLevel = options.getSchemaInfoLevel();
      final DatabaseInfoRetriever retriever =
          new DatabaseInfoRetriever(retrieverConnection, catalog);

      LOGGER.log(Level.INFO, "Crawling SchemaCrawler information");

      LOGGER.log(Level.INFO, "Retrieving database information");

      stopWatch.time(
          "retrieveDatabaseInfo",
          () -> {
            retriever.retrieveDatabaseInfo();
            return null;
          });

      stopWatch.time(
          "retrieveAdditionalDatabaseInfo",
          () -> {
            if (infoLevel.isRetrieveAdditionalDatabaseInfo()) {
              retriever.retrieveAdditionalDatabaseInfo();
            } else {
              LOGGER.log(
                  Level.INFO,
                  "Not retrieving additional database information, since this was not requested");
            }
            return null;
          });

      LOGGER.log(Level.INFO, "Retrieving JDBC driver information");
      stopWatch.time(
          "retrieveJdbcDriverInfo",
          () -> {
            retriever.retrieveJdbcDriverInfo();
            return null;
          });

      stopWatch.time(
          "retrieveAdditionalJdbcDriverInfo",
          () -> {
            if (infoLevel.isRetrieveAdditionalJdbcDriverInfo()) {
              retriever.retrieveAdditionalJdbcDriverInfo();
            } else {
              LOGGER.log(
                  Level.INFO,
                  "Not retrieving additional JDBC driver information, since this was not requested");
            }
            return null;
          });

      LOGGER.log(Level.INFO, "Retrieving SchemaCrawler crawl information");
      stopWatch.time(
          "retrieveCrawlHeaderInfo",
          () -> {
            retriever.retrieveCrawlHeaderInfo(options.getTitle());
            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 database information", e);
      }
    }
  }
Beispiel #15
0
  private void analyseSchema() {
    try {
      final String jdbcUrl = m_options.m_jdbcUrl;
      final String schemaName = m_options.m_schemaName;

      System.out.printf("Analysing schema [%s] at [%s]...%n", schemaName, jdbcUrl);
      final long msecStart = System.currentTimeMillis();

      // Create a database connection
      final DataSource dataSource = new DatabaseConnectionOptions(jdbcUrl);
      final Connection connection =
          dataSource.getConnection(m_options.m_username, m_options.m_password);

      // Create the options
      final SchemaCrawlerOptions options = new SchemaCrawlerOptions();

      // Set what details are required in the schema - this affects the
      // time taken to crawl the schema
      options.setSchemaInfoLevel(SchemaInfoLevel.standard());
      options.setSchemaInclusionRule(new RegularExpressionInclusionRule(schemaName));

      final InclusionRule tableRule = new RegularExpressionExclusionRule(".+[\\$/].+");

      final InclusionRule procRule =
          s -> {
            // System.out.println (s);
            final String[] a =
                new String[] {
                  ".*" + "\\$" + ".*",
                  ".*" + "_RETRIGGER_EXPRESSION" + ".*",
                  ".*" + "BITOR" + ".*",
                  ".*" + "CENTRED" + ".*",
                  ".*" + "COMPLEMENT" + ".*",
                  ".*" + "D2" + ".*",
                  ".*" + "DDMMYYYYHHMMMSS" + ".*",
                  ".*" + "DOW" + ".*",
                  ".*" + "DSM" + ".*",
                  ".*" + "ENABLE" + ".*",
                  ".*" + "F2" + ".*",
                  ".*" + "GET_LAST_CHANGE_NUMBER" + ".*",
                  ".*" + "GROOM" + ".*",
                  ".*" + "IS_EVEN" + ".*",
                  ".*" + "IS_ODD" + ".*",
                  ".*" + "LEFT_PADDED" + ".*",
                  ".*" + "LOGICAL_" + ".*",
                  ".*" + "M2" + ".*",
                  ".*" + "MD5" + ".*",
                  ".*" + "MERGE_OSS_TRIP_LOCATION" + ".*",
                  ".*" + "MODF" + ".*",
                  ".*" + "NEXT_TIME_ABSOLUTE" + ".*",
                  ".*" + "NEXT_TIME_RELATIVE" + ".*",
                  ".*" + "RESERVE_FARM_STATUS" + ".*",
                  ".*" + "RIGHT_PADDED" + ".*",
                  ".*" + "S2" + ".*",
                  ".*" + "SCHEDULE_JOB" + ".*",
                  ".*" + "SNDF" + ".*",
                  ".*" + "SSM" + ".*",
                  ".*" + "XOR" + ".*",
                  ".*" + "XSD_TIMESTAMP" + ".*",
                  ".*" + "//dummy//" + ".*"
                };
            return !HcUtil.containsWildcard(a, s);
          };
      options.setTableInclusionRule(tableRule);

      options.setRoutineInclusionRule(procRule); // new ExcludeAll ()

      // Get the schema definition
      final Catalog catalog = SchemaCrawlerUtility.getCatalog(connection, options);
      final long msecDuration = System.currentTimeMillis() - msecStart;

      final Collection<Schema> schemas = catalog.getSchemas();
      ThreadContext.assertFault(
          schemas.size() == 1, "Expected one schema, got %s [%s]", schemas.size(), schemas.size());

      final Schema[] a = schemas.toArray(new Schema[schemas.size()]);
      final Schema schema = a[0];

      final DatabaseInfo di = catalog.getDatabaseInfo();

      System.out.printf(
          "  catalog[%s] database[%s] version[%s]%n",
          schema.getFullName(), di.getProductName(), di.getProductVersion());
      System.out.printf("  %s tables%n", catalog.getTables(schema).size());
      System.out.printf("  %s stored procedures%n", catalog.getRoutines(schema).size());
      System.out.printf("...analysed in %s seconds%n", msecDuration / 1_000L);

      outputSchemaXml(catalog, schema);
      System.out.printf("Schema file [%s] created.%n", m_options.m_outputFilename);
    } catch (final SchemaCrawlerException | SQLException e) {
      // Propagate exception as unchecked fault up to the fault barrier.
      ThreadContext.throwFault(e);
    }
  }
  @Test
  public void counts() throws Exception {

    final int[] tableCounts = {
      6, 0, 0, 2, 0,
    };
    final int[][] tableColumnCounts = {
      {9, 3, 3, 6, 1, 2}, {}, {}, {4, 5}, {},
    };
    final int[][] checkConstraints = {
      {4, 0, 2, 3, 0, 1, 0}, {}, {}, {4, 2}, {},
    };
    final int[][] indexCounts = {
      {
        3, 0, 3, 1, 0, 1,
      },
      {},
      {},
      {1, 1},
      {},
    };
    final int[][] fkCounts = {
      {
        1, 0, 2, 1, 0, 0,
      },
      {},
      {},
      {1, 1},
      {},
    };
    final int[][] exportedFkCounts = {
      {
        1, 0, 0, 1, 0, 0,
      },
      {},
      {},
      {1, 0},
      {},
    };
    final int[][] importedFkCounts = {
      {
        0, 0, 2, 0, 0, 0,
      },
      {},
      {},
      {0, 1},
      {},
    };
    final int[][] tablePrivilegesCounts = {
      {6, 6, 6, 6, 6, 6, 6},
      {},
      {},
      {
        6, 6,
      },
      {},
    };

    final InformationSchemaViews informationSchemaViews = new InformationSchemaViews();
    informationSchemaViews.setTableConstraintsSql(
        "SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS");
    informationSchemaViews.setCheckConstraintsSql(
        "SELECT * FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS");

    final SchemaCrawlerOptions schemaCrawlerOptions = new SchemaCrawlerOptions();
    schemaCrawlerOptions.setSchemaInfoLevel(SchemaInfoLevel.maximum());
    schemaCrawlerOptions.setInformationSchemaViews(informationSchemaViews);
    schemaCrawlerOptions.setSchemaInclusionRule(
        new InclusionRule(InclusionRule.ALL, ".*\\.FOR_LINT"));

    final Database database = getDatabase(schemaCrawlerOptions);
    final Schema[] schemas = database.getSchemas().toArray(new Schema[0]);
    assertEquals("Schema count does not match", 5, schemas.length);
    for (int schemaIdx = 0; schemaIdx < schemas.length; schemaIdx++) {
      final Schema schema = schemas[schemaIdx];
      final Table[] tables = database.getTables(schema).toArray(new Table[0]);
      assertEquals("Table count does not match", tableCounts[schemaIdx], tables.length);
      Arrays.sort(tables, NamedObjectSort.alphabetical);
      for (int tableIdx = 0; tableIdx < tables.length; tableIdx++) {
        final Table table = tables[tableIdx];
        assertEquals(
            String.format(
                "Table [%d][%d] %s columns count does not match",
                schemaIdx, tableIdx, table.getFullName()),
            tableColumnCounts[schemaIdx][tableIdx],
            table.getColumns().size());
        assertEquals(
            String.format(
                "Table [%d][%d] %s check constraints count does not match",
                schemaIdx, tableIdx, table.getFullName()),
            checkConstraints[schemaIdx][tableIdx],
            table.getCheckConstraints().size());
        assertEquals(
            String.format(
                "Table [%d][%d] %s index count does not match",
                schemaIdx, tableIdx, table.getFullName()),
            indexCounts[schemaIdx][tableIdx],
            table.getIndices().size());
        assertEquals(
            String.format(
                "Table [%d][%d] %s foreign key count does not match",
                schemaIdx, tableIdx, table.getFullName()),
            fkCounts[schemaIdx][tableIdx],
            table.getForeignKeys().size());
        assertEquals(
            String.format(
                "Table [%d][%d] %s exported foreign key count does not match",
                schemaIdx, tableIdx, table.getFullName()),
            exportedFkCounts[schemaIdx][tableIdx],
            table.getExportedForeignKeys().size());
        assertEquals(
            String.format(
                "Table [%d][%d] %s imported foreign key count does not match",
                schemaIdx, tableIdx, table.getFullName()),
            importedFkCounts[schemaIdx][tableIdx],
            table.getImportedForeignKeys().size());
        assertEquals(
            String.format(
                "Table [%d][%d] %s privileges count does not match",
                schemaIdx, tableIdx, table.getFullName()),
            tablePrivilegesCounts[schemaIdx][tableIdx],
            table.getPrivileges().size());
      }
    }
  }
  @Test
  public void compareCompositeOutput() throws Exception {
    final String queryCommand1 = "all_tables";
    final Config queriesConfig = new Config();
    queriesConfig.put(queryCommand1, "SELECT * FROM INFORMATION_SCHEMA.SYSTEM_TABLES");
    final String queryCommand2 = "dump_tables";
    queriesConfig.put(
        queryCommand2, "SELECT ${orderbycolumns} FROM ${table} ORDER BY ${orderbycolumns}");

    final String[] commands =
        new String[] {
          SchemaTextDetailType.details + "," + Operation.count + "," + Operation.dump,
          SchemaTextDetailType.list + "," + Operation.count,
          queryCommand1
              + ","
              + queryCommand2
              + ","
              + Operation.count
              + ","
              + SchemaTextDetailType.list,
        };

    final List<String> failures = new ArrayList<String>();
    for (final OutputFormat outputFormat : OutputFormat.values()) {
      for (final String command : commands) {
        final String referenceFile = command + "." + outputFormat.name();

        final File testOutputFile =
            File.createTempFile("schemacrawler." + referenceFile + ".", ".test");
        testOutputFile.delete();

        final OutputOptions outputOptions = new OutputOptions(outputFormat.name(), testOutputFile);
        outputOptions.setNoInfo(false);
        outputOptions.setNoHeader(false);
        outputOptions.setNoFooter(false);

        final Config config =
            Config.load(
                SchemaCrawlerOutputTest.class.getResourceAsStream(
                    "/hsqldb.INFORMATION_SCHEMA.config.properties"));
        final SchemaCrawlerOptions schemaCrawlerOptions = new SchemaCrawlerOptions(config);
        schemaCrawlerOptions.setSchemaInfoLevel(SchemaInfoLevel.maximum());

        final DatabaseConnectionOptions connectionOptions =
            testUtility.getDatabaseConnectionOptions();

        final SchemaCrawlerExecutable executable = new SchemaCrawlerExecutable(command);
        executable.setSchemaCrawlerOptions(schemaCrawlerOptions);
        executable.setOutputOptions(outputOptions);
        executable.setAdditionalConfiguration(queriesConfig);
        executable.execute(connectionOptions.getConnection());

        failures.addAll(
            TestUtility.compareOutput(
                COMPOSITE_OUTPUT + referenceFile, testOutputFile, outputFormat));
      }
    }
    if (failures.size() > 0) {
      fail(failures.toString());
    }
  }
  @Test
  public void checkConstraints() throws Exception {

    final int[] tableCounts = {6, 5, 0, 0, 2, 0};
    final int[][] checkConstraintCounts = {
      {4, 0, 2, 3, 0, 1, 0},
      {
        3, 0, 0, 3, 6,
      },
      {},
      {},
      {4, 2},
      {}
    };
    final String[][][] checkConstraintNames = {
      {
        {"CHECK_UPPERCASE_STATE", "SYS_CT_10068", "SYS_CT_10069", "SYS_CT_10070"},
        {},
        {"SYS_CT_10083", "SYS_CT_10084"},
        {"SYS_CT_10076", "SYS_CT_10077", "SYS_CT_10078"},
        {},
        {"SYS_CT_10065"},
        {}
      },
      {
        {"SYS_CT_10144", "SYS_CT_10145", "SYS_CT_10146"},
        {},
        {},
        {
          "SYS_CT_10133", "SYS_CT_10134", "SYS_CT_10135",
        },
        {
          "CHECK_UPPERCASE_STATE",
          "SYS_CT_10121",
          "SYS_CT_10122",
          "SYS_CT_10123",
          "SYS_CT_10124",
          "SYS_CT_10125",
        },
      },
      {},
      {},
      {
        {"SYS_CT_10105", "SYS_CT_10106", "SYS_CT_10107", "SYS_CT_10108"},
        {"SYS_CT_10114", "SYS_CT_10115"}
      },
      {}
    };

    final InformationSchemaViews informationSchemaViews = new InformationSchemaViews();
    informationSchemaViews.setTableConstraintsSql(
        "SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS");
    informationSchemaViews.setCheckConstraintsSql(
        "SELECT * FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS");

    final SchemaCrawlerOptions schemaCrawlerOptions = new SchemaCrawlerOptions();
    schemaCrawlerOptions.setSchemaInfoLevel(SchemaInfoLevel.maximum());
    schemaCrawlerOptions.setInformationSchemaViews(informationSchemaViews);

    final Database database = getDatabase(schemaCrawlerOptions);
    final Schema[] schemas = database.getSchemas().toArray(new Schema[0]);
    assertEquals("Schema count does not match", 6, schemas.length);
    for (int schemaIdx = 0; schemaIdx < schemas.length; schemaIdx++) {
      final Schema schema = schemas[schemaIdx];
      final Table[] tables = database.getTables(schema).toArray(new Table[0]);
      assertEquals("Table count does not match", tableCounts[schemaIdx], tables.length);
      for (int tableIdx = 0; tableIdx < tables.length; tableIdx++) {
        final Table table = tables[tableIdx];
        final CheckConstraint[] checkConstraints =
            table.getCheckConstraints().toArray(new CheckConstraint[0]);
        assertEquals(
            String.format(
                "Table [%d][%d] %s check constraints count does not match",
                schemaIdx, tableIdx, table.getFullName()),
            checkConstraintCounts[schemaIdx][tableIdx],
            checkConstraints.length);
        for (int i = 0; i < checkConstraints.length; i++) {
          final CheckConstraint checkConstraint = checkConstraints[i];
          assertEquals(
              "Check constraint name does not match for table " + table,
              checkConstraintNames[schemaIdx][tableIdx][i],
              checkConstraint.getName());
        }
      }
    }
  }