absl::Status TableValidator::ValidateUpdate()

in backend/schema/validators/table_validator.cc [390:511]


absl::Status TableValidator::ValidateUpdate(const Table* table,
                                            const Table* old_table,
                                            SchemaValidationContext* context) {
  if (table->is_deleted()) {
    ZETASQL_RET_CHECK(!table->owner_index_ || table->owner_index_->is_deleted());
    if (!table->child_tables_.empty()) {
      // Build a sorted list of interleaved child tables and indexes.
      std::vector<std::string> interleaved_tables;
      std::vector<std::string> interleaved_indices;
      for (const auto& entry : table->child_tables_) {
        if (entry->owner_index()) {
          interleaved_indices.push_back(entry->owner_index()->Name());
        } else {
          interleaved_tables.push_back(entry->Name());
        }
      }
      std::sort(interleaved_tables.begin(), interleaved_tables.end());
      std::sort(interleaved_indices.begin(), interleaved_indices.end());

      // Cannot drop a table with interleaved child tables or indexes.
      if (!interleaved_tables.empty()) {
        return error::DropTableWithInterleavedTables(
            table->name_, absl::StrJoin(interleaved_tables, ","));
      } else if (!interleaved_indices.empty()) {
        return error::DropTableWithDependentIndices(
            table->name_, absl::StrJoin(interleaved_indices, ","));
      }
    }
    if (!table->indexes_.empty()) {
      return error::DropTableWithDependentIndices(
          table->name_,
          absl::StrJoin(table->indexes_.begin(), table->indexes_.end(), ",",
                        [](std::string* out, const Index* child) {
                          return out->append(child->Name());
                        }));
    }
    if (!table->change_streams_explicitly_tracking_table().empty()) {
      return error::DropTableWithDependentChangeStreams(
          table->name_,
          absl::StrJoin(table->change_streams().begin(),
                        table->change_streams().end(), ",",
                        [](std::string* out, const ChangeStream* child) {
                          return out->append(child->Name());
                        }));
    }
    context->global_names()->RemoveName(table->Name());
    if (!table->synonym().empty()) {
      context->global_names()->RemoveName(table->synonym());
    }
    return absl::OkStatus();
  }

  // ID should not change during cloning, but the name can.
  ZETASQL_RET_CHECK_EQ(table->id(), old_table->id());

  if (table->is_public() && context->is_postgresql_dialect()) {
    ZETASQL_RET_CHECK(table->postgresql_oid().has_value());
    ZETASQL_RET_CHECK(old_table->postgresql_oid().has_value());
    ZETASQL_RET_CHECK_EQ(table->postgresql_oid().value(),
                 old_table->postgresql_oid().value());
  } else {
    ZETASQL_RET_CHECK(!table->postgresql_oid().has_value());
    ZETASQL_RET_CHECK(!old_table->postgresql_oid().has_value());
  }

  if (table->owner_change_stream_) {
    ZETASQL_RET_CHECK(!table->owner_change_stream_->is_deleted());
  }

  if (table->owner_index_) {
    ZETASQL_RET_CHECK(!table->owner_index_->is_deleted());
  }

  // Check additional constraints on new columns.
  for (const Column* column : table->columns()) {
    // Ignore old columns.
    if (old_table->FindColumn(column->Name()) != nullptr) {
      continue;
    }

    // New columns cannot be nullable unless it is a generated column or
    // it has a default value. Index stored columns of the index data table
    // could be as non nullable as the source table is, but these checks
    // (nullable, generated, default value) do not apply.
    if (!table->owner_index_ && !column->is_nullable() &&
        !column->is_generated() && !column->has_default_value()) {
      return error::AddingNotNullColumn(table->name_, column->Name());
    }
  }

  // Cannot drop key columns, change their order or nullability.
  ZETASQL_RET_CHECK_EQ(table->primary_key_.size(), old_table->primary_key_.size());
  for (int i = 0; i < table->primary_key_.size(); ++i) {
    if (table->primary_key_[i]->is_deleted()) {
      return error::InvalidDropKeyColumn(
          table->primary_key_[i]->column()->Name(), table->name_);
    }
    ZETASQL_RET_CHECK_EQ(table->primary_key_[i]->is_descending(),
                 old_table->primary_key()[i]->is_descending());
    if (table->primary_key_[i]->column()->is_nullable() !=
        old_table->primary_key()[i]->column()->is_nullable()) {
      std::string reason = absl::Substitute(
          "from $0 to $1",
          old_table->primary_key()[i]->column()->is_nullable() ? "NULL"
                                                               : "NOT NULL",
          table->primary_key_[i]->column()->is_nullable() ? "NULL"
                                                          : "NOT NULL");
      return error::CannotChangeKeyColumn(
          absl::StrCat(table->name_, ".",
                       table->primary_key_[i]->column()->Name()),
          reason);
    }
  }

  if (!table->synonym().empty() && !old_table->synonym().empty() &&
      table->synonym() != old_table->synonym()) {
    return error::CannotAlterSynonym(table->synonym(), table->name_);
  }

  ZETASQL_RETURN_IF_ERROR(ValidateUpdateRowDeletionPolicy(table, old_table));
  return absl::OkStatus();
}