@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);
  }
  /**
   * Retrieves additional table attributes from the database.
   *
   * @throws SQLException On a SQL exception
   */
  void retrieveAdditionalTableAttributes() throws SQLException {
    final InformationSchemaViews informationSchemaViews =
        getRetrieverConnection().getInformationSchemaViews();
    if (!informationSchemaViews.hasAdditionalTableAttributesSql()) {
      LOGGER.log(Level.FINE, "Additional table attributes SQL statement was not provided");
      return;
    }
    final String tableAttributesSql = informationSchemaViews.getAdditionalTableAttributesSql();

    final Connection connection = getDatabaseConnection();
    try (final Statement statement = connection.createStatement();
        final MetadataResultSet results =
            new MetadataResultSet(statement.executeQuery(tableAttributesSql)); ) {

      while (results.next()) {
        final String catalogName = quotedName(results.getString("TABLE_CATALOG"));
        final String schemaName = quotedName(results.getString("TABLE_SCHEMA"));
        final String tableName = quotedName(results.getString("TABLE_NAME"));
        LOGGER.log(Level.FINER, "Retrieving additional table attributes: " + tableName);

        final MutableTable table = lookupTable(catalogName, schemaName, tableName);
        if (table == null) {
          LOGGER.log(
              Level.FINE,
              String.format("Cannot find table, %s.%s.%s", catalogName, schemaName, tableName));
          continue;
        }

        table.addAttributes(results.getAttributes());
      }
    } catch (final Exception e) {
      LOGGER.log(Level.WARNING, "Could not retrieve additional table attributes", e);
    }
  }
  @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());
    }
  }
  void retrieveForeignKeys(final NamedObjectList<MutableTable> allTables)
      throws SchemaCrawlerSQLException {
    requireNonNull(allTables);

    final InformationSchemaViews informationSchemaViews =
        getRetrieverConnection().getInformationSchemaViews();

    if (!informationSchemaViews.hasForeignKeysSql()) {
      LOGGER.log(Level.INFO, "Retrieving foreign keys using database metadata");
      retrieveForeignKeysUsingDatabaseMetadata(allTables);
    } else {
      LOGGER.log(Level.INFO, "Retrieving foreign keys using SQL");
      retrieveForeignKeysUsingSql(informationSchemaViews);
    }
  }
  @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(""));
  }
  /**
   * Retrieves a view information from the database, in the INFORMATION_SCHEMA format.
   *
   * @throws SQLException On a SQL exception
   */
  void retrieveViewInformation() throws SQLException {
    final InformationSchemaViews informationSchemaViews =
        getRetrieverConnection().getInformationSchemaViews();

    if (!informationSchemaViews.hasViewsSql()) {
      LOGGER.log(Level.FINE, "Views SQL statement was not provided");
      return;
    }
    final String viewInformationSql = informationSchemaViews.getViewsSql();

    final Connection connection = getDatabaseConnection();
    try (final Statement statement = connection.createStatement();
        final MetadataResultSet results =
            new MetadataResultSet(statement.executeQuery(viewInformationSql)); ) {

      while (results.next()) {
        final String catalogName = quotedName(results.getString("TABLE_CATALOG"));
        final String schemaName = quotedName(results.getString("TABLE_SCHEMA"));
        final String viewName = quotedName(results.getString("TABLE_NAME"));

        final MutableView view = (MutableView) lookupTable(catalogName, schemaName, viewName);
        if (view == null) {
          LOGGER.log(
              Level.FINE,
              String.format("Cannot find table, %s.%s.%s", catalogName, schemaName, viewName));
          continue;
        }

        LOGGER.log(Level.FINER, "Retrieving view information: " + viewName);
        final String definition = results.getString("VIEW_DEFINITION");
        final CheckOptionType checkOption =
            results.getEnum("CHECK_OPTION", CheckOptionType.unknown);
        final boolean updatable = results.getBoolean("IS_UPDATABLE");

        view.appendDefinition(definition);
        view.setCheckOption(checkOption);
        view.setUpdatable(updatable);

        view.addAttributes(results.getAttributes());
      }
    } catch (final Exception e) {
      LOGGER.log(Level.WARNING, "Could not retrieve views", e);
    }
  }
  @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()));
    }
  }
  private void retrieveForeignKeysUsingSql(final InformationSchemaViews informationSchemaViews)
      throws SchemaCrawlerSQLException {
    final String fkSql = informationSchemaViews.getForeignKeysSql();
    LOGGER.log(Level.FINER, String.format("Executing SQL to retrieve foreign keys: %n%s", fkSql));

    final NamedObjectList<MutableForeignKey> foreignKeys = new NamedObjectList<>();
    final Connection connection = getDatabaseConnection();
    try (final Statement statement = connection.createStatement();
        final MetadataResultSet results = new MetadataResultSet(executeSql(statement, fkSql)); ) {
      createForeignKeys(results, foreignKeys);
    } catch (final SQLException e) {
      throw new SchemaCrawlerSQLException("Could not retrieve foreign keys from SQL:\n" + fkSql, e);
    }
  }
  /**
   * Retrieves a trigger information from the database, in the INFORMATION_SCHEMA format.
   *
   * @throws SQLException On a SQL exception
   */
  void retrieveTriggerInformation() throws SQLException {
    final InformationSchemaViews informationSchemaViews =
        getRetrieverConnection().getInformationSchemaViews();
    if (!informationSchemaViews.hasTriggerSql()) {
      LOGGER.log(Level.FINE, "Trigger definition SQL statement was not provided");
      return;
    }
    final String triggerInformationSql = informationSchemaViews.getTriggersSql();

    final Connection connection = getDatabaseConnection();
    try (final Statement statement = connection.createStatement();
        final MetadataResultSet results =
            new MetadataResultSet(statement.executeQuery(triggerInformationSql)); ) {

      while (results.next()) {
        final String catalogName = quotedName(results.getString("TRIGGER_CATALOG"));
        final String schemaName = quotedName(results.getString("TRIGGER_SCHEMA"));
        final String triggerName = quotedName(results.getString("TRIGGER_NAME"));
        LOGGER.log(Level.FINER, "Retrieving trigger: " + triggerName);

        // "EVENT_OBJECT_CATALOG", "EVENT_OBJECT_SCHEMA"
        final String tableName = results.getString("EVENT_OBJECT_TABLE");

        final MutableTable table = lookupTable(catalogName, schemaName, tableName);
        if (table == null) {
          LOGGER.log(
              Level.FINE,
              String.format("Cannot find table, %s.%s.%s", catalogName, schemaName, tableName));
          continue;
        }

        final EventManipulationType eventManipulationType =
            results.getEnum("EVENT_MANIPULATION", EventManipulationType.unknown);
        final int actionOrder = results.getInt("ACTION_ORDER", 0);
        final String actionCondition = results.getString("ACTION_CONDITION");
        final String actionStatement = results.getString("ACTION_STATEMENT");
        final ActionOrientationType actionOrientation =
            results.getEnum("ACTION_ORIENTATION", ActionOrientationType.unknown);
        String conditionTimingString = results.getString("ACTION_TIMING");
        if (conditionTimingString == null) {
          conditionTimingString = results.getString("CONDITION_TIMING");
        }
        final ConditionTimingType conditionTiming =
            ConditionTimingType.valueOfFromValue(conditionTimingString);

        MutableTrigger trigger = table.lookupTrigger(triggerName);
        if (trigger == null) {
          trigger = new MutableTrigger(table, triggerName);
        }
        trigger.setEventManipulationType(eventManipulationType);
        trigger.setActionOrder(actionOrder);
        trigger.appendActionCondition(actionCondition);
        trigger.appendActionStatement(actionStatement);
        trigger.setActionOrientation(actionOrientation);
        trigger.setConditionTiming(conditionTiming);

        trigger.addAttributes(results.getAttributes());
        // Add trigger to the table
        table.addTrigger(trigger);
      }
    } catch (final Exception e) {
      LOGGER.log(Level.WARNING, "Could not retrieve triggers", e);
    }
  }
  /**
   * Retrieves a check constraint information from the database, in the INFORMATION_SCHEMA format.
   *
   * @throws SQLException On a SQL exception
   */
  void retrieveCheckConstraintInformation() throws SQLException {
    final Map<String, MutableCheckConstraint> checkConstraintsMap = new HashMap<>();

    final InformationSchemaViews informationSchemaViews =
        getRetrieverConnection().getInformationSchemaViews();

    if (!informationSchemaViews.hasTableConstraintsSql()) {
      LOGGER.log(Level.FINE, "Table constraints SQL statement was not provided");
      return;
    }
    final String tableConstraintsInformationSql = informationSchemaViews.getTableConstraintsSql();

    final Connection connection = getDatabaseConnection();
    try (final Statement statement = connection.createStatement();
        final MetadataResultSet results =
            new MetadataResultSet(statement.executeQuery(tableConstraintsInformationSql)); ) {

      while (results.next()) {
        final String catalogName = quotedName(results.getString("CONSTRAINT_CATALOG"));
        final String schemaName = quotedName(results.getString("CONSTRAINT_SCHEMA"));
        final String constraintName = quotedName(results.getString("CONSTRAINT_NAME"));
        LOGGER.log(Level.FINER, "Retrieving constraint: " + constraintName);
        // "TABLE_CATALOG", "TABLE_SCHEMA"
        final String tableName = quotedName(results.getString("TABLE_NAME"));

        final MutableTable table = lookupTable(catalogName, schemaName, tableName);
        if (table == null) {
          LOGGER.log(
              Level.FINE,
              String.format("Cannot find table, %s.%s.%s", catalogName, schemaName, tableName));
          continue;
        }

        final String constraintType = results.getString("CONSTRAINT_TYPE");
        final boolean deferrable = results.getBoolean("IS_DEFERRABLE");
        final boolean initiallyDeferred = results.getBoolean("INITIALLY_DEFERRED");

        if (constraintType.equalsIgnoreCase("check")) {
          final MutableCheckConstraint checkConstraint =
              new MutableCheckConstraint(table, constraintName);
          checkConstraint.setDeferrable(deferrable);
          checkConstraint.setInitiallyDeferred(initiallyDeferred);

          checkConstraint.addAttributes(results.getAttributes());

          // Add to map, since we will need this later
          final String constraintFullName = table.getSchema().getFullName() + "." + constraintName;
          checkConstraintsMap.put(constraintFullName, checkConstraint);
        }
      }
    } catch (final Exception e) {
      LOGGER.log(Level.WARNING, "Could not retrieve check constraint information", e);
      return;
    }

    if (!informationSchemaViews.hasCheckConstraintsSql()) {
      LOGGER.log(Level.FINE, "Check constraints SQL statement was not provided");
      return;
    }
    final String checkConstraintInformationSql = informationSchemaViews.getCheckConstraintsSql();

    // Get check constraint definitions
    try (final Statement statement = connection.createStatement();
        final MetadataResultSet results =
            new MetadataResultSet(statement.executeQuery(checkConstraintInformationSql)); ) {
      while (results.next()) {
        final String catalogName = quotedName(results.getString("CONSTRAINT_CATALOG"));
        final String schemaName = quotedName(results.getString("CONSTRAINT_SCHEMA"));
        final String constraintName = quotedName(results.getString("CONSTRAINT_NAME"));
        LOGGER.log(Level.FINER, "Retrieving constraint definition: " + constraintName);
        String definition = results.getString("CHECK_CLAUSE");

        final String constraintFullName =
            new SchemaReference(catalogName, schemaName) + "." + constraintName;
        final MutableCheckConstraint checkConstraint = checkConstraintsMap.get(constraintFullName);
        if (checkConstraint == null) {
          LOGGER.log(Level.FINEST, "Could not add check constraint to table: " + constraintName);
          continue;
        }
        final String text = checkConstraint.getDefinition();
        if (!Utility.isBlank(text)) {
          definition = checkConstraint.getDefinition() + definition;
        }

        checkConstraint.setDefinition(definition);
      }
    } catch (final Exception e) {
      LOGGER.log(Level.WARNING, "Could not retrieve check constraints", e);
    }

    // Add check constraints to tables
    final Collection<MutableCheckConstraint> checkConstraintsCollection =
        checkConstraintsMap.values();
    for (final MutableCheckConstraint checkConstraint : checkConstraintsCollection) {
      final MutableTable table = (MutableTable) checkConstraint.getParent();
      table.addCheckConstraint(checkConstraint);
    }
  }
  @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());
        }
      }
    }
  }
  @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());
      }
    }
  }