absl::Status ColumnValidator::ValidateUpdate()

in backend/schema/validators/column_validator.cc [302:473]


absl::Status ColumnValidator::ValidateUpdate(const Column* column,
                                             const Column* old_column,
                                             SchemaValidationContext* context) {
  // if column has row deletion policy, then can't delete the column or change
  // type.
  bool has_row_deletion_policy =
      column->table()->row_deletion_policy().has_value() &&
      column->table()->row_deletion_policy()->column_name() == column->Name();
  if (has_row_deletion_policy && !column->table_->is_deleted() &&
      (column->is_deleted() || !column->GetType()->IsTimestamp())) {
    return error::RowDeletionPolicyWillBreak(column->Name(),
                                             column->table()->Name());
  }

  if (!column->change_streams_explicitly_tracking_column().empty() &&
      column->is_deleted()) {
    std::string change_stream_names;
    for (int i = 0;
         i < column->change_streams_explicitly_tracking_column().size(); ++i) {
      change_stream_names.append(
          column->change_streams_explicitly_tracking_column()[i]->Name());
    }
    return error::DropColumnWithChangeStream(
        column->table()->Name(), column->Name(),
        column->change_streams_explicitly_tracking_column().size(),
        change_stream_names);
  }
  if (column->is_deleted()) {
    return absl::OkStatus();
  }

  // Once set, column ID should never change.
  ZETASQL_RET_CHECK_EQ(column->id(), old_column->id());

  // For a non-deleted column, the objects it depends on should
  // also be alive.
  ZETASQL_RET_CHECK(!column->table_->is_deleted());
  // It is invalid to drop a column which is referenced by a generated column.
  for (const Column* dep : column->dependent_columns()) {
    if (dep->is_deleted()) {
      return error::InvalidDropColumnReferencedByGeneratedColumn(
          dep->Name(), column->table()->Name(), column->Name());
    }
  }
  if (column->is_generated() && !old_column->is_generated()) {
    return error::CannotConvertRegularColumnToGeneratedColumn(
        column->table()->Name(), column->Name());
  }
  if (!column->is_generated() && old_column->is_generated()) {
    return error::CannotConvertGeneratedColumnToRegularColumn(
        column->table()->Name(), column->Name());
  }
  if (column->is_generated() && old_column->is_generated()) {
    if (!column->GetType()->Equals(old_column->GetType())) {
      return error::CannotAlterStoredGeneratedColumnDataType(
          column->table()->Name(), column->Name());
    }
    if (column->expression().value() != old_column->expression().value()) {
      return error::CannotAlterGeneratedColumnExpression(
          column->table()->Name(), column->Name());
    }
    if (column->is_stored() != old_column->is_stored()) {
      return error::CannotAlterGeneratedColumnStoredAttribute(
          column->table()->Name(), column->Name());
    }
  }
  if (!column->GetType()->Equals(old_column->GetType())) {
    for (const Column* generated_column : column->table()->columns()) {
      if (generated_column->is_generated()) {
        for (const Column* dep : generated_column->dependent_columns()) {
          if (column == dep) {
            return error::
                CannotAlterColumnDataTypeWithDependentStoredGeneratedColumn(
                    column->Name());
          }
        }
      }
    }
  }

  if (column->source_column_) {
    // There is no valid scenario under which a source column drop should
    // trigger a cascading drop on referencing column.
    if (column->source_column_->is_deleted()) {
      ZETASQL_RET_CHECK_NE(column->table_->owner_index(), nullptr);
      return error::InvalidDropColumnWithDependency(
          column->name_, column->table_->owner_index()->indexed_table()->Name(),
          column->table_->owner_index()->Name());
    }
  }

  if (old_column->is_nullable_ && !column->is_nullable_) {
    context->AddAction([old_column](const SchemaValidationContext* context) {
      return VerifyColumnNotNull(old_column->table(), old_column, context);
    });
  }

  // Check for size reduction and type change.
  ZETASQL_RETURN_IF_ERROR(CheckAllowedColumnTypeChange(
      old_column, column, old_column->GetType(), column->type_, context));

  if (column->type_->IsTimestamp()) {
    if (column->allows_commit_timestamp() &&
        !old_column->allows_commit_timestamp()) {
      context->AddAction([column](const SchemaValidationContext* context) {
        return VerifyColumnCommitTimestamp(column->table_, column, context);
      });
    }
  }

  for (const SchemaNode* dependency : column->sequences_used()) {
    // Cannot drop a sequence if a column depends on it.
    if (dependency->is_deleted()) {
      const auto& dep_info = dependency->GetSchemaNameInfo();
      std::string dependency_type =
          (dep_info->global ? absl::AsciiStrToUpper(dep_info->kind)
                            : absl::AsciiStrToLower(dep_info->kind));
      return error::InvalidDropDependentColumn(dependency_type, dep_info->name,
                                               column->FullName());
    }
  }

  for (const SchemaNode* dependency : column->udf_dependencies()) {
    if (dependency->is_deleted()) {
      const auto& dep_info = dependency->GetSchemaNameInfo();
      std::string dependency_type =
          (dep_info->global ? absl::AsciiStrToUpper(dep_info->kind)
                            : absl::AsciiStrToLower(dep_info->kind));
      return error::InvalidDropDependentColumn(dependency_type, dep_info->name,
                                               column->FullName());
    }
  }

  if (context->is_postgresql_dialect()) {
    // Default and generated columns must have OIDs.
    if (old_column->is_generated() || old_column->has_default_value()) {
      ZETASQL_RET_CHECK(old_column->postgresql_oid().has_value());
    }
    if (column->is_generated() || column->has_default_value()) {
      ZETASQL_RET_CHECK(column->postgresql_oid().has_value());
    }
    // Alter statement may change the default value which would be assigned a
    // new OID so don't assert that the OIDs are the same.
  } else {
    ZETASQL_RET_CHECK(!old_column->postgresql_oid().has_value());
    ZETASQL_RET_CHECK(!column->postgresql_oid().has_value());
  }

  for (const SchemaNode* dep : column->udf_dependencies()) {
    // TODO When dropping support is added, a check should be added
    // to ensure that this column references a UDF that is also being dropped.
    if (context->IsModifiedNode(dep)) {
      const auto& dep_info = dep->GetSchemaNameInfo();
      std::string dependency_type =
          (dep_info->global ? absl::AsciiStrToUpper(dep_info->kind)
                            : absl::AsciiStrToLower(dep_info->kind));
      std::string modify_action = absl::StrCat("alter ", dependency_type);

      std::string dependency_name;
      if (auto dep_udf = dep->As<const Udf>(); dep_udf != nullptr) {
        dependency_name = dep_udf->Name();
      }
      // No need to check modifications on index dependencies as indexes
      // cannot currently be altered.
      ZETASQL_RETURN_IF_ERROR(ValidateColumnSignatureChange(
          modify_action, dependency_name, column, column->table(),
          context->tmp_new_schema(), context->type_factory()));
    }
  }

  return absl::OkStatus();
}