/**
   * This helper method handles the case when a tuple is being removed from the table, before the
   * row has actually been removed from the table. All indexes on the table are updated to remove
   * the row.
   *
   * @param tblFileInfo details of the table being updated
   * @param ptup the tuple about to be removed from the table
   */
  private void removeRowFromIndexes(TableInfo tblFileInfo, PageTuple ptup) {

    logger.debug(
        "Removing tuple " + ptup + " from indexes for table " + tblFileInfo.getTableName());

    // Iterate over the indexes in the table.
    TableSchema schema = tblFileInfo.getSchema();
    for (ColumnRefs indexDef : schema.getIndexes().values()) {
      TupleLiteral idxTup =
          IndexUtils.makeSearchKeyValue(indexDef, ptup, /* findExactTuple */ true);

      logger.debug("Removing tuple " + idxTup + " from index " + indexDef.getIndexName());

      try {
        IndexInfo indexInfo = indexManager.openIndex(tblFileInfo, indexDef.getIndexName());

        TupleFile tupleFile = indexInfo.getTupleFile();

        PageTuple idxPageTup = IndexUtils.findTupleInIndex(idxTup, tupleFile);
        if (idxPageTup == null) {
          throw new IllegalStateException(
              "Can't find tuple in " + "index corresponding to table's tuple.");
        }

        tupleFile.deleteTuple(idxPageTup);
      } catch (IOException e) {
        throw new EventDispatchException(
            "Couldn't update index "
                + indexDef.getIndexName()
                + " for table "
                + tblFileInfo.getTableName());
      }
    }
  }
  // Inherit interface docs.
  @Override
  public void closeTable(TableInfo tableInfo) throws IOException {
    // Remove this table from the cache since it's about to be closed.
    openTables.remove(tableInfo.getTableName());

    DBFile dbFile = tableInfo.getTupleFile().getDBFile();

    // Flush all open pages for the table.
    storageManager.getBufferManager().flushDBFile(dbFile);
    storageManager.getFileManager().closeDBFile(dbFile);
  }
  // Inherit interface docs.
  @Override
  public void analyzeTable(TableInfo tableInfo) throws IOException {
    // Analyze the table's tuple-file.
    tableInfo.getTupleFile().analyze();

    // TODO:  Probably want to analyze all the indexes associated with
    //        the table as well...
  }
  /**
   * This helper method handles the case when a tuple is being added to the table, after the row has
   * already been added to the table. All indexes on the table are updated to include the new row.
   *
   * @param tblFileInfo details of the table being updated
   * @param ptup the new tuple that was inserted into the table
   */
  private void addRowToIndexes(TableInfo tblFileInfo, PageTuple ptup) {
    logger.debug("Adding tuple " + ptup + " to indexes for table " + tblFileInfo.getTableName());

    // Iterate over the indexes in the table.
    TableSchema schema = tblFileInfo.getSchema();
    for (ColumnRefs indexDef : schema.getIndexes().values()) {
      TupleLiteral idxTup;
      try {
        IndexInfo indexInfo = indexManager.openIndex(tblFileInfo, indexDef.getIndexName());

        TupleFile tupleFile = indexInfo.getTupleFile();

        TableConstraintType constraintType = indexDef.getConstraintType();
        if (constraintType != null && constraintType.isUnique()) {
          // Check if the index already has a tuple with this value.
          idxTup = IndexUtils.makeSearchKeyValue(indexDef, ptup, /* findExactTuple */ false);

          if (IndexUtils.findTupleInIndex(idxTup, tupleFile) != null) {
            // Adding this row would violate the unique index.
            throw new IllegalStateException(
                "Unique index " + "already contains a tuple with this value.");
          }
        }

        idxTup = IndexUtils.makeSearchKeyValue(indexDef, ptup, /* findExactTuple */ true);

        logger.debug("Adding tuple " + idxTup + " to index " + indexDef.getIndexName());

        tupleFile.addTuple(idxTup);
      } catch (IOException e) {
        throw new EventDispatchException(
            "Couldn't update index "
                + indexDef.getIndexName()
                + " for table "
                + tblFileInfo.getTableName(),
            e);
      }
    }
  }
  private void dropTableIndexes(TableInfo tableInfo) throws IOException {
    String tableName = tableInfo.getTableName();

    // We need to open the table so that we can drop all related objects,
    // such as indexes on the table, etc.
    TableSchema schema = tableInfo.getTupleFile().getSchema();

    // Check whether this table is referenced by any other tables via
    // foreign keys.  Need to check the primary key and candidate keys.

    KeyColumnRefs pk = schema.getPrimaryKey();
    if (pk != null && !pk.getReferencingIndexes().isEmpty()) {
      throw new IOException(
          "Drop table failed due to the table" + " having foreign key dependencies:  " + tableName);
    }

    List<KeyColumnRefs> candKeyList = schema.getCandidateKeys();
    for (KeyColumnRefs candKey : candKeyList) {
      if (!candKey.getReferencingIndexes().isEmpty()) {
        throw new IOException(
            "Drop table failed due to the table"
                + " having foreign key dependencies:  "
                + tableName);
      }
    }

    // If we got here, the table being dropped is not a referenced table.
    // Start cleaning up various schema objects for the table.

    List<KeyColumnRefs> parentCandKeyList;
    TableInfo parentTableInfo;
    // Now drop the foreign key constraint fields that this table
    // may have on other parent tables. Scan through this table's
    // Foreign key indexes to see which tables need maintenance
    List<ForeignKeyColumnRefs> forKeyList = schema.getForeignKeys();
    for (ForeignKeyColumnRefs forKey : forKeyList) {
      // Open the parent table, and iterate through all of its primary
      // and candidate keys, dropping any foreign key constraints that
      // refer to the child table with tableName
      String parentTableName = forKey.getRefTable();
      try {
        parentTableInfo = openTable(parentTableName);
      } catch (Exception e) {
        throw new IOException(
            "The referenced table, "
                + parentTableName
                + ", which is referenced by "
                + tableName
                + ", does not exist.");
      }

      TableSchema parentSchema = parentTableInfo.getTupleFile().getSchema();
      KeyColumnRefs parentPK = parentSchema.getPrimaryKey();

      if (parentPK != null) {
        parentPK.dropRefToTable(tableName);
      }

      parentCandKeyList = parentSchema.getCandidateKeys();
      for (KeyColumnRefs parentCandKey : parentCandKeyList) {
        parentCandKey.dropRefToTable(tableName);
      }

      // Persist the changes we made to the schema
      saveTableInfo(parentTableInfo);
    }

    // Then drop the indexes since we've checked the constraints

    IndexManager indexManager = storageManager.getIndexManager();
    for (String indexName : schema.getIndexes().keySet())
      indexManager.dropIndex(tableInfo, indexName);
  }
 // Inherit interface docs.
 @Override
 public void saveTableInfo(TableInfo tableInfo) throws IOException {
   TupleFile tupleFile = tableInfo.getTupleFile();
   TupleFileManager manager = tupleFile.getManager();
   manager.saveMetadata(tupleFile);
 }