# frozen_string_literal: true

#
# Copyright (C) 2011 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

# rubocop:disable Migration/ChangeColumn, Migration/Execute, Migration/IdColumn
# rubocop:disable Migration/PrimaryKey, Migration/RootAccountId, Rails/CreateTableWithTimestamps
# rubocop:disable Rails/ThreeStateBooleanColumn
class InitCanvasDb < ActiveRecord::Migration[7.0]
  prepend Canvas::ActiveRecord::Migration::DeferForeignKeys

  tag :predeploy

  def create_aua_log_partition(index)
    table_name = :"aua_logs_#{index}"
    create_table table_name do |t|
      t.references :asset_user_access, null: false, index: false
      t.timestamp :created_at, null: false
    end
    # Intentionally not adding FK on asset_user_access_id as the records are transient
    # and we're trying to do as little work as possible on the insert to these
    # and can be thrown away if they don't match anything anyway as the log is compacted.
  end

  def up
    connection.transaction(requires_new: true) do
      create_extension(:pg_trgm, schema: connection.shard.name, if_not_exists: true)
    rescue ActiveRecord::StatementInvalid
      raise ActiveRecord::Rollback
    end

    execute(<<~SQL.squish)
      CREATE FUNCTION #{connection.quote_table_name("setting_as_int")}( IN p_setting TEXT ) RETURNS INT4 as $$
      DECLARE
          v_text text;
          v_int8 int8;
      BEGIN
          v_text := current_setting( p_setting, true );

          IF v_text IS NULL THEN
              RETURN NULL;
          END IF;

          IF NOT v_text ~ '^-?[0-9]{1,10}$' THEN
              RETURN NULL;
          END IF;

          v_int8 := v_text::INT8;
          IF v_int8 > 2147483647 OR v_int8 < -2147483648 THEN
              RETURN NULL;
          END IF;
          RETURN v_int8::int4;
      END;
      $$ language plpgsql;
    SQL

    execute(<<~SQL.squish)
      CREATE FUNCTION #{connection.quote_table_name("guard_excessive_updates")}() RETURNS TRIGGER AS $BODY$
      DECLARE
          record_count integer;
          max_record_count integer;
      BEGIN
          SELECT count(*) FROM oldtbl INTO record_count;
          max_record_count := COALESCE(setting_as_int('inst.max_update_limit.' || TG_TABLE_NAME), setting_as_int('inst.max_update_limit'), '#{PostgreSQLAdapterExtensions::DEFAULT_MAX_UPDATE_LIMIT}');
          IF record_count > max_record_count THEN
            IF current_setting('inst.max_update_fail', true) IS NOT DISTINCT FROM 'true' THEN
                RAISE EXCEPTION 'guard_excessive_updates: % to %.% failed', TG_OP, TG_TABLE_SCHEMA, TG_TABLE_NAME USING DETAIL = 'Would update ' || record_count || ' records but max is ' || max_record_count || ', orig query: ' || current_query();
            ELSE
                RAISE WARNING 'guard_excessive_updates: % to %.% was dangerous', TG_OP, TG_TABLE_SCHEMA, TG_TABLE_NAME USING DETAIL = 'Updated ' || record_count || ' records but threshold is ' || max_record_count || ', orig query: ' || current_query();
            END IF;
          END IF;
          RETURN NULL;
      END
      $BODY$ LANGUAGE plpgsql;
    SQL
    set_search_path("guard_excessive_updates")

    metadata = ActiveRecord::Base.internal_metadata
    metadata[:guard_dangerous_changes_installed] = "true"

    # there may already be tables from plugins
    connection.tables.grep_v(/^_/).each do |table|
      add_guard_excessive_updates(table)
    end

    # these tables are referenced the most, so it's nice to put them first so we
    # don't have to defer creation of foreign keys to them
    create_table :accounts do |t|
      t.string :name, limit: 255
      t.timestamps precision: nil
      t.string :workflow_state, default: "active", null: false, limit: 255
      t.timestamp :deleted_at
      t.references :parent_account, foreign_key: { to_table: :accounts }, index: false
      t.string :sis_source_id, limit: 255
      t.references :sis_batch, foreign_key: true, index: { where: "sis_batch_id IS NOT NULL" }
      t.references :current_sis_batch, index: false
      t.references :root_account, null: false, foreign_key: { to_table: :accounts, deferrable: :immediate }, index: false
      t.references :last_successful_sis_batch, index: false
      t.string :membership_types, limit: 255
      t.string :default_time_zone, limit: 255
      t.string :external_status, default: "active", limit: 255
      t.bigint :storage_quota
      t.bigint :default_storage_quota
      t.string :allowed_services, limit: 255
      t.text :turnitin_pledge
      t.text :turnitin_comments
      t.string :turnitin_account_id, limit: 255
      t.string :turnitin_salt, limit: 255
      t.string :turnitin_crypted_secret, limit: 255
      t.boolean :show_section_name_as_course_name, default: false
      t.boolean :allow_sis_import, default: false
      t.string :equella_endpoint, limit: 255
      t.text :settings
      t.string :uuid, limit: 255, index: { unique: true }
      t.string :default_locale, limit: 255
      t.text :stuck_sis_fields
      t.bigint :default_user_storage_quota
      t.string :lti_guid, limit: 255
      t.bigint :default_group_storage_quota
      t.string :turnitin_host, limit: 255
      t.string :integration_id, limit: 255
      t.string :lti_context_id, limit: 255, index: { unique: true }
      t.string :brand_config_md5, limit: 32, index: { where: "brand_config_md5 IS NOT NULL" }
      t.string :turnitin_originality, limit: 255
      t.string :account_calendar_subscription_type,
               default: "manual",
               null: false,
               limit: 255,
               index: { where: "account_calendar_subscription_type <> 'manual'" }
      t.references :latest_outcome_import,
                   foreign_key: { to_table: :outcome_imports },
                   index: { where: "latest_outcome_import_id IS NOT NULL" }
      t.references :course_template,
                   foreign_key: { to_table: :courses },
                   index: { where: "course_template_id IS NOT NULL" }
      t.boolean :account_calendar_visible, default: false, null: false
      t.references :grading_standard, foreign_key: true, index: { where: "grading_standard_id IS NOT NULL" }

      t.replica_identity_index
      t.index [:name, :parent_account_id]
      t.index [:parent_account_id, :root_account_id]
      t.index [:sis_source_id, :root_account_id], where: "sis_source_id IS NOT NULL", unique: true
      t.index [:integration_id, :root_account_id],
              unique: true,
              name: "index_accounts_on_integration_id",
              where: "integration_id IS NOT NULL"

      t.foreign_key :brand_configs, column: :brand_config_md5, primary_key: :md5
    end

    create_table :cloned_items do |t|
      t.bigint :original_item_id
      t.string :original_item_type, limit: 255
      t.timestamps precision: nil
    end

    create_table :courses do |t|
      t.string :name, limit: 255
      t.references :account, null: false, foreign_key: true
      t.string :group_weighting_scheme, limit: 255
      t.string :workflow_state, null: false, limit: 255
      t.string :uuid, limit: 255, index: true
      t.timestamp :start_at
      t.timestamp :conclude_at
      t.references :grading_standard, index: false
      t.boolean :is_public
      t.boolean :allow_student_wiki_edits
      t.timestamps precision: nil
      t.boolean :show_public_context_messages
      t.text :syllabus_body, limit: 16_777_215
      t.boolean :allow_student_forum_attachments, default: false
      t.string :default_wiki_editing_roles, limit: 255
      t.references :wiki, foreign_key: true, index: { where: "wiki_id IS NOT NULL" }
      t.boolean :allow_student_organized_groups, default: true
      t.string :course_code, limit: 255
      t.string :default_view, limit: 255
      t.references :abstract_course, foreign_key: true, index: { where: "abstract_course_id IS NOT NULL" }
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.references :enrollment_term, null: false, foreign_key: { deferrable: :immediate }
      t.string :sis_source_id, limit: 255
      t.references :sis_batch, foreign_key: true, index: { where: "sis_batch_id IS NOT NULL" }
      t.boolean :open_enrollment
      t.bigint :storage_quota
      t.text :tab_configuration
      t.boolean :allow_wiki_comments
      t.text :turnitin_comments
      t.boolean :self_enrollment
      t.string :license, limit: 255
      t.boolean :indexed
      t.boolean :restrict_enrollments_to_course_dates
      t.references :template_course, foreign_key: { to_table: :courses }
      t.string :locale, limit: 255
      t.text :settings
      t.references :replacement_course, index: false
      t.text :stuck_sis_fields
      t.text :public_description
      t.string :self_enrollment_code, limit: 255, index: { unique: true, where: "self_enrollment_code IS NOT NULL" }
      t.integer :self_enrollment_limit
      t.string :integration_id, limit: 255
      t.string :time_zone, limit: 255
      t.string :lti_context_id, limit: 255, index: { unique: true }
      t.bigint :turnitin_id, unique: true
      t.boolean :show_announcements_on_home_page
      t.integer :home_page_announcement_limit
      t.references :latest_outcome_import,
                   foreign_key: { to_table: :outcome_imports },
                   index: { where: "latest_outcome_import_id IS NOT NULL" }
      t.string :grade_passback_setting, limit: 255
      t.boolean :template, default: false, null: false
      t.boolean :homeroom_course, default: false, null: false, index: { where: "homeroom_course" }
      t.boolean :sync_enrollments_from_homeroom, default: false, null: false, index: { where: "sync_enrollments_from_homeroom" }
      t.references :homeroom_course, index: { where: "homeroom_course_id IS NOT NULL" }
      t.timestamp :deleted_at, precision: 6
      t.datetime :archived_at
      t.boolean :horizon_course, null: false, default: false

      t.replica_identity_index
      t.index [:sis_source_id, :root_account_id], where: "sis_source_id IS NOT NULL", unique: true
      t.index :grading_standard_id, name: "index_courses_on_grading_standard"
      if (trgm = connection.extension(:pg_trgm)&.schema)
        t.index "(
            coalesce(lower(name), '') || ' ' ||
            coalesce(lower(sis_source_id), '') || ' ' ||
            coalesce(lower(course_code), '')
          ) #{trgm}.gin_trgm_ops",
                name: "index_gin_trgm_courses_composite_search",
                using: :gin
        t.index [:integration_id, :root_account_id],
                unique: true,
                name: "index_courses_on_integration_id",
                where: "integration_id IS NOT NULL"
      end
      t.index :archived_at, where: "archived_at IS NOT NULL"
    end

    create_table :course_reports do |t|
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.references :course, foreign_key: true, null: false
      t.references :user, foreign_key: true, null: false
      t.references :attachment, foreign_key: true, index: { where: "attachment_id IS NOT NULL" }

      t.string :workflow_state, default: "created", null: false
      t.string :report_type, null: false
      t.text :parameters
      t.text :message
      t.bigint :job_ids, array: true, default: [], null: false

      t.timestamp :start_at
      t.timestamp :end_at
      t.timestamps

      t.replica_identity_index
    end

    create_table :sis_batches do |t|
      t.references :account, null: false, index: false
      t.timestamp :ended_at
      t.string :workflow_state, null: false, limit: 255
      t.text :data
      t.timestamps precision: nil
      t.references :attachment
      t.integer :progress
      t.text :processing_errors, limit: 16_777_215
      t.text :processing_warnings, limit: 16_777_215
      t.boolean :batch_mode
      t.references :batch_mode_term,
                   foreign_key: { to_table: :enrollment_terms },
                   index: { where: "batch_mode_term_id IS NOT NULL" }
      t.text :options
      t.references :user, foreign_key: true, index: { where: "user_id IS NOT NULL" }
      t.timestamp :started_at
      t.string :diffing_data_set_identifier, limit: 255
      t.boolean :diffing_remaster
      t.references :generated_diff, index: false
      t.references :errors_attachment, foreign_key: { to_table: :attachments }
      t.integer :change_threshold
      t.boolean :diffing_threshold_exceeded, default: false, null: false
      t.bigint :job_ids, array: true, default: [], null: false

      t.index [:account_id, :created_at], name: "index_sis_batches_account_id_created_at"
      t.index %i[account_id diffing_data_set_identifier created_at],
              name: "index_sis_batches_diffing"
      t.index %i[account_id workflow_state created_at], name: "index_sis_batches_workflow_state_for_accounts"
    end

    create_table :users do |t|
      t.string :name, limit: 255
      t.string :sortable_name, limit: 255
      t.string :workflow_state, null: false, limit: 255, index: true
      t.string :time_zone, limit: 255
      t.string :uuid, limit: 255, index: { unique: true, name: "index_users_on_unique_uuid" }
      t.timestamps precision: nil
      t.string :avatar_image_url, limit: 255
      t.string :avatar_image_source, limit: 255
      t.timestamp :avatar_image_updated_at
      t.string :phone, limit: 255
      t.string :school_name, limit: 255
      t.string :school_position, limit: 255
      t.string :short_name, limit: 255
      t.timestamp :deleted_at
      t.boolean :show_user_services, default: true
      t.integer :reminder_time_for_due_dates, default: 172_800
      t.integer :reminder_time_for_grading, default: 0
      t.bigint :storage_quota
      t.string :visible_inbox_types, limit: 255
      t.boolean :subscribe_to_emails
      t.text :features_used
      t.text :preferences
      t.string :avatar_state, limit: 255
      t.string :locale, limit: 255
      t.string :browser_locale, limit: 255
      t.integer :unread_conversations_count, default: 0
      t.text :stuck_sis_fields
      t.boolean :public
      t.string :otp_secret_key_enc, limit: 255
      t.string :otp_secret_key_salt, limit: 255
      t.references :otp_communication_channel, index: false
      t.string :initial_enrollment_type, limit: 255
      t.integer :crocodoc_id
      t.timestamp :last_logged_out
      t.string :lti_context_id, limit: 255, index: { unique: true }
      t.bigint :turnitin_id, index: { unique: true, where: "turnitin_id IS NOT NULL" }
      t.text :lti_id, index: { unique: true, name: "index_users_on_unique_lti_id" }
      t.string :pronouns
      t.bigint :root_account_ids, array: true, null: false, default: []
      t.references :merged_into_user,
                   foreign_key: { to_table: :users },
                   index: { where: "merged_into_user_id IS NOT NULL" }
      t.string :instructure_identity_id, limit: 255, index: { unique: true }

      t.replica_identity_index :root_account_ids
      t.index [:avatar_state, :avatar_image_updated_at]
      if (trgm = connection.extension(:pg_trgm)&.schema)
        t.index "lower(name) #{trgm}.gin_trgm_ops", name: "index_gin_trgm_users_name", using: :gin
        t.index "LOWER(short_name) #{trgm}.gin_trgm_ops", name: "index_gin_trgm_users_short_name", using: :gin
        t.index "LOWER(name) #{trgm}.gin_trgm_ops",
                name: "index_gin_trgm_users_name_active_only",
                using: :gin,
                where: "workflow_state IN ('registered', 'pre_registered')"
      end
      t.index "(sortable_name COLLATE public.\"und-u-kn-true\"), id",
              name: :index_users_on_sortable_name
      t.index :id, where: "workflow_state <> 'deleted'", name: "index_active_users_on_id"
    end

    # most the rest is alphabetical, with a "natural" sort order (so that for example quizzes comes before quiz_submissions)
    create_table :abstract_courses do |t|
      t.string :sis_source_id, limit: 255, index: true
      t.references :sis_batch, foreign_key: true, index: { where: "sis_batch_id IS NOT NULL" }
      t.references :account, null: false, foreign_key: true
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.string :short_name, limit: 255
      t.string :name, limit: 255
      t.timestamps precision: nil
      t.references :enrollment_term, null: false, foreign_key: true
      t.string :workflow_state, null: false, limit: 255
      t.text :stuck_sis_fields

      t.index [:root_account_id, :sis_source_id]
    end

    create_table :access_tokens do |t|
      t.references :developer_key, null: false, index: false
      t.references :user, foreign_key: true
      t.timestamp :last_used_at
      t.timestamp :expires_at
      t.string :purpose, limit: 255
      t.timestamps precision: nil
      t.string :crypted_token, limit: 255, index: { unique: true }
      t.string :token_hint, limit: 255
      t.text :scopes
      t.boolean :remember_access
      t.string :crypted_refresh_token, limit: 255, index: { unique: true }
      t.string :workflow_state, default: "active", null: false, index: true
      t.references :root_account, null: false, index: false
      t.references :real_user,
                   foreign_key: { to_table: :users },
                   index: { where: "real_user_id IS NOT NULL" }
      t.timestamp :permanent_expires_at

      t.replica_identity_index
      t.index [:developer_key_id, :last_used_at]
    end

    create_table :account_notifications do |t|
      t.string :subject, limit: 255
      t.string :icon, default: "warning", limit: 255
      t.text :message
      t.references :account, null: false, foreign_key: true, index: false
      t.references :user, null: false, foreign_key: true
      t.timestamp :start_at, null: false
      t.timestamp :end_at, null: false
      t.timestamps precision: nil
      t.string :required_account_service, limit: 255
      t.integer :months_in_display_cycle
      t.boolean :domain_specific, default: false, null: false
      t.boolean :send_message, default: false, null: false
      t.timestamp :messages_sent_at
      t.string :workflow_state, default: "active", null: false, limit: 255

      t.index %i[account_id end_at start_at], name: "index_account_notifications_by_account_and_timespan"
    end

    create_table :account_notification_roles do |t|
      t.references :account_notification, null: false, foreign_key: true, index: false
      t.references :role,
                   foreign_key: true,
                   index: { where: "role_id IS NOT NULL", name: "index_account_notification_roles_only_on_role_id" }
      t.string :workflow_state, default: "active", null: false, limit: 255

      t.index [:account_notification_id, :role_id],
              unique: true,
              name: "index_account_notification_roles_on_role_id"
    end

    create_table :account_reports do |t|
      t.references :user, null: false, foreign_key: true
      t.text :message
      t.references :account, null: false, foreign_key: true, index: false
      t.references :attachment, foreign_key: true
      t.string :workflow_state, default: "created", null: false, limit: 255
      t.string :report_type, limit: 255
      t.integer :progress
      t.timestamps precision: nil
      t.text :parameters
      t.integer :current_line
      t.integer :total_lines
      t.timestamp :start_at
      t.timestamp :end_at
      t.bigint :job_ids, array: true, default: [], null: false

      t.index %i[account_id report_type created_at],
              order: { created_at: :desc },
              name: "index_account_reports_latest_of_type_per_account"
    end

    create_table :account_report_runners do |t|
      t.references :account_report, null: false, foreign_key: true
      t.string :workflow_state, null: false, default: "created", limit: 255
      t.string :batch_items, array: true, default: []
      t.timestamps precision: nil
      t.timestamp :started_at
      t.timestamp :ended_at
      t.bigint :job_ids, array: true, default: [], null: false

      t.index :created_at
    end

    create_table :account_report_rows do |t|
      t.references :account_report, null: false, foreign_key: true
      t.references :account_report_runner, null: false, foreign_key: true
      t.integer :row_number
      t.string :row, array: true, default: []
      t.timestamp :created_at, null: false, index: true
      t.string :file, index: true
    end

    create_table :account_users do |t|
      t.references :account, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: true
      t.timestamps precision: nil
      t.references :role, null: false, foreign_key: true
      t.string :workflow_state, default: "active", null: false, index: true
      t.references :sis_batch, index: { where: "sis_batch_id IS NOT NULL" }
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false

      t.replica_identity_index
    end

    create_table :alerts do |t|
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.text :recipients, null: false
      t.integer :repetition
      t.timestamps precision: nil
    end

    create_table :alert_criteria do |t|
      t.references :alert, foreign_key: true, index: { where: "alert_id IS NOT NULL" }
      t.string :criterion_type, limit: 255
      t.float :threshold
    end

    create_table :anonymous_or_moderation_events do |t|
      t.references :assignment, null: false, foreign_key: true
      t.references :user, foreign_key: true
      t.references :submission, foreign_key: true
      t.references :canvadoc, foreign_key: true
      t.string :event_type, null: false
      t.jsonb :payload, null: false, default: {}
      t.timestamps precision: nil
      t.references :context_external_tool,
                   foreign_key: { name: "fk_rails_f492821432" },
                   index: { where: "context_external_tool_id IS NOT NULL",
                            name: "index_ame_on_context_external_tool_id" }
      t.references :quiz,
                   foreign_key: { name: "fk_rails_a862303024" },
                   index: { where: "quiz_id IS NOT NULL" }
    end

    create_table :appointment_groups do |t|
      t.string :title, limit: 255
      t.text :description
      t.string :location_name, limit: 255
      t.string :location_address, limit: 255
      t.string :context_code, limit: 255
      t.string :sub_context_code, limit: 255
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.timestamp :start_at
      t.timestamp :end_at
      t.integer :participants_per_appointment
      t.integer :max_appointments_per_participant # nil means no limit
      t.integer :min_appointments_per_participant, default: 0
      t.string :participant_visibility, limit: 255
      t.boolean :allow_observer_signup, null: false, default: false
    end

    create_table :appointment_group_contexts do |t|
      t.references :appointment_group
      t.string :context_code, limit: 255
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.timestamps precision: nil
    end

    create_table :appointment_group_sub_contexts do |t|
      t.references :appointment_group
      t.bigint :sub_context_id
      t.string :sub_context_type, limit: 255
      t.string :sub_context_code, limit: 255
      t.timestamps precision: nil
    end

    create_table :assessment_question_banks do |t|
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.text :title
      t.string :workflow_state, null: false, limit: 255
      t.timestamp :deleted_at
      t.timestamps precision: nil
      t.string :migration_id, limit: 255
      t.references :root_account, foreign_key: { to_table: :accounts }

      t.index %i[context_id context_type title id],
              name: "index_aqb_context_and_title"
    end

    create_table :assessment_question_bank_users do |t|
      t.references :assessment_question_bank, null: false, index: { name: "assessment_qbu_aqb_id" }
      t.references :user, null: false, index: { name: "assessment_qbu_u_id" }
      t.timestamps precision: nil
    end

    create_table :assessment_questions do |t|
      t.text :name
      t.text :question_data
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.string :workflow_state, limit: 255
      t.timestamps null: true, precision: nil
      t.references :assessment_question_bank, index: false
      t.timestamp :deleted_at
      t.string :migration_id, limit: 255
      t.integer :position
      t.references :root_account, foreign_key: { to_table: :accounts }

      t.index [:assessment_question_bank_id, :position], name: "question_bank_id_and_position"
    end

    create_table :assessment_requests do |t|
      t.references :rubric_assessment
      t.references :user, null: false, foreign_key: true
      t.references :asset, null: false, foreign_key: { to_table: :submissions }, index: false
      t.string :asset_type, null: false, limit: 255
      t.bigint :assessor_asset_id, null: false
      t.string :assessor_asset_type, null: false, limit: 255
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.string :uuid, limit: 255
      t.references :rubric_association, foreign_key: true
      t.references :assessor, null: false, foreign_key: { to_table: :users }

      t.index [:assessor_asset_id, :assessor_asset_type], name: "aa_id_and_aa_type"
      t.index [:asset_id, :asset_type]
    end

    create_table :asset_user_accesses do |t|
      t.string :asset_code, limit: 255
      t.string :asset_group_code, limit: 255
      t.references :user, index: false
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.timestamp :last_access
      t.timestamps null: true, precision: nil
      t.string :asset_category, limit: 255
      t.float :view_score
      t.float :participate_score
      t.string :action_level, limit: 255
      t.text :display_name
      t.string :membership_type, limit: 255
      t.references :root_account, index: false, null: false

      t.replica_identity_index
      t.index [:user_id, :asset_code]
      t.index %i[context_id context_type user_id updated_at], name: "index_asset_user_accesses_on_ci_ct_ui_ua"
      t.index %i[user_id context_id asset_code id],
              name: "index_asset_user_accesses_on_user_id_context_id_asset_code"

      # add a partial index on the updated_at column for rows where context_type
      # is 'Group', optimizing queries filtering by this context
      t.index :updated_at,
              name: "index_on_updated_at_for_group_context",
              where: "context_type = 'Group'"
    end

    # one table for each day of week, they'll periodically
    # be compacted and truncated.  This prevents having to
    # create and drop true partitions at a high rate
    (0..6).each { |i| create_aua_log_partition(i) }

    create_table :assignment_groups do |t|
      t.string :name, limit: 255
      t.text :rules
      t.string :default_assignment_name, limit: 255
      t.integer :position
      t.string :assignment_weighting_scheme, limit: 255
      t.float :group_weight
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.references :cloned_item, foreign_key: true, index: { where: "cloned_item_id IS NOT NULL" }
      t.string :context_code, limit: 255
      t.string :migration_id, limit: 255
      t.string :sis_source_id, limit: 255
      t.text :integration_data
      t.references :root_account, foreign_key: { to_table: :accounts }

      t.index [:context_id, :context_type]
    end

    create_table :assignments do |t|
      t.string :title, limit: 255
      t.text :description, limit: 16_777_215
      t.timestamp :due_at
      t.timestamp :unlock_at
      t.timestamp :lock_at
      t.float :points_possible
      t.float :min_score
      t.float :max_score
      t.float :mastery_score
      t.string :grading_type, limit: 255
      t.string :submission_types, limit: 255
      t.string :workflow_state, null: false, limit: 255, default: "published", index: true
      t.bigint :context_id, null: false, index: { where: "context_type='Course' AND workflow_state<>'deleted'",
                                                  name: "index_assignments_active" }
      t.string :context_type, null: false, limit: 255
      t.references :assignment_group
      t.references :grading_standard
      t.timestamps null: true, precision: nil
      t.integer :submissions_downloads, default: 0
      t.integer :peer_review_count, default: 0
      t.timestamp :peer_reviews_due_at
      t.boolean :peer_reviews_assigned, default: false, null: false
      t.boolean :peer_reviews, default: false, null: false
      t.boolean :automatic_peer_reviews, default: false, null: false
      t.boolean :all_day, default: false, null: false
      t.date :all_day_date
      t.boolean :could_be_locked, default: false, null: false
      t.references :cloned_item, foreign_key: true, index: { where: "cloned_item_id IS NOT NULL" }
      t.integer :position
      t.string :migration_id, limit: 255
      t.boolean :grade_group_students_individually, default: false, null: false
      t.boolean :anonymous_peer_reviews, default: false, null: false
      t.string :time_zone_edited, limit: 255
      t.boolean :turnitin_enabled, default: false, null: false
      t.string :allowed_extensions, limit: 255
      t.text :turnitin_settings
      t.boolean :muted, default: false, null: false
      t.references :group_category, foreign_key: true, index: { where: "group_category_id IS NOT NULL" }
      t.boolean :freeze_on_copy, default: false, null: false
      t.boolean :copied, default: false, null: false
      t.boolean :only_visible_to_overrides, default: false, null: false
      t.boolean :post_to_sis, default: false, null: false
      t.string :integration_id, limit: 255
      t.text :integration_data
      t.bigint :turnitin_id, index: { unique: true, where: "turnitin_id IS NOT NULL" }
      t.boolean :moderated_grading, default: false, null: false
      t.timestamp :grades_published_at
      t.boolean :omit_from_final_grade, default: false, null: false
      t.boolean :vericite_enabled, default: false, null: false
      t.boolean :intra_group_peer_reviews, default: false, null: false
      t.string :lti_context_id, index: { unique: true }
      t.boolean :anonymous_instructor_annotations, default: false, null: false
      t.references :duplicate_of,
                   index: { where: "duplicate_of_id IS NOT NULL" },
                   foreign_key: { to_table: :assignments }
      t.boolean :anonymous_grading, default: false
      t.boolean :graders_anonymous_to_graders, default: false
      t.integer :grader_count, default: 0
      t.boolean :grader_comments_visible_to_graders, default: true
      t.references :grader_section,
                   foreign_key: { to_table: :course_sections },
                   index: { where: "grader_section_id IS NOT NULL" }
      t.references :final_grader,
                   foreign_key: { to_table: :users },
                   index: { where: "final_grader_id IS NOT NULL" }
      t.boolean :grader_names_visible_to_final_grader, default: true
      t.timestamp :duplication_started_at, index: { where: "duplication_started_at IS NOT NULL AND workflow_state = 'duplicating'" }
      t.timestamp :importing_started_at, index: { where: "importing_started_at IS NOT NULL AND workflow_state = 'importing'" }
      t.integer :allowed_attempts
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }
      t.string :sis_source_id
      t.references :migrate_from,
                   foreign_key: { to_table: :quizzes },
                   index: { where: "migrate_from_id IS NOT NULL" }
      t.jsonb :settings
      t.references :annotatable_attachment,
                   foreign_key: { to_table: :attachments },
                   index: { where: "annotatable_attachment_id IS NOT NULL" }
      t.boolean :important_dates, default: false, null: false, index: { where: "important_dates" }
      t.boolean :hide_in_gradebook, default: false, null: false
      t.string :ab_guid, array: true, default: [], null: false
      t.references :parent_assignment, foreign_key: { to_table: :assignments }
      t.string :type, null: false, limit: 255, default: "Assignment"
      t.string :sub_assignment_tag, limit: 255
      t.boolean :has_sub_assignments, null: false, default: false
      t.boolean :rubric_self_assessment_enabled, null: false, default: false

      t.index [:context_id, :context_type]
      t.index [:sis_source_id, :root_account_id], where: "sis_source_id IS NOT NULL", unique: true
      t.index :duplication_started_at,
              where: "workflow_state = 'migrating' AND duplication_started_at IS NOT NULL",
              name: "index_assignments_duplicating_on_started_at"
      t.index %i[context_id grading_standard_id grading_type],
              name: "index_assignments_on_context_grading_standard_grading_type"
    end

    create_table :assignment_configuration_tool_lookups do |t|
      t.references :assignment, null: false, foreign_key: true
      t.bigint :tool_id
      t.string :tool_type, null: false, limit: 255
      t.string :subscription_id
      t.string :tool_product_code
      t.string :tool_vendor_code
      t.string :tool_resource_type_code
      t.string :context_type, default: "Account", null: false

      t.index %i[tool_id tool_type assignment_id], unique: true, name: "index_tool_lookup_on_tool_assignment_id"
      t.index %i[tool_product_code tool_vendor_code tool_resource_type_code], name: "index_resource_codes_on_assignment_configuration_tool_lookups"
    end

    create_table :assignment_overrides do |t|
      t.timestamps precision: nil
      # generic info
      t.references :assignment, foreign_key: true
      t.integer :assignment_version
      t.references :set,
                   polymorphic: { limit: 255 },
                   index: { name: "index_assignment_overrides_on_set_type_and_set_id" }
      t.string :title, null: false, limit: 255
      t.string :workflow_state, null: false, limit: 255
      # due at override
      t.boolean :due_at_overridden, default: false, null: false
      t.timestamp :due_at, index: { where: "due_at_overridden", name: "index_assignment_overrides_due_at_when_overridden" }
      t.boolean :all_day
      t.date :all_day_date
      # unlock at override
      t.boolean :unlock_at_overridden, default: false, null: false
      t.timestamp :unlock_at
      # lock at override
      t.boolean :lock_at_overridden, default: false, null: false
      t.timestamp :lock_at
      t.references :quiz, foreign_key: true
      t.integer :quiz_version
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.references :context_module, foreign_key: true, index: { where: "context_module_id IS NOT NULL" }
      t.boolean :unassign_item, default: false, null: false
      t.references :wiki_page, foreign_key: true, index: { where: "wiki_page_id IS NOT NULL" }
      t.references :discussion_topic, foreign_key: true, index: { where: "discussion_topic_id IS NOT NULL" }
      t.references :attachment, foreign_key: true, index: { where: "attachment_id IS NOT NULL" }
      t.references :parent_override, foreign_key: { to_table: :assignment_overrides }, index: { where: "parent_override_id IS NOT NULL" }

      t.check_constraint <<~SQL.squish, name: "chk_require_context"
        workflow_state='deleted' OR
        assignment_id IS NOT NULL OR
        quiz_id IS NOT NULL OR context_module_id IS NOT NULL OR
        wiki_page_id IS NOT NULL OR
        discussion_topic_id IS NOT NULL OR
        attachment_id IS NOT NULL
      SQL

      t.index %i[assignment_id set_type set_id],
              name: "index_assignment_overrides_on_assignment_and_set",
              unique: true,
              where: "workflow_state='active' and set_id is not null"
      t.index %i[context_module_id set_id set_type],
              unique: true,
              where: "context_module_id IS NOT NULL AND workflow_state = 'active' AND set_id IS NOT NULL",
              name: "index_assignment_overrides_on_context_module_id_and_set"
      t.index %i[wiki_page_id set_id set_type],
              unique: true,
              where: "wiki_page_id IS NOT NULL AND workflow_state = 'active' AND set_id IS NOT NULL",
              name: "index_assignment_overrides_on_wiki_page_id_and_set"
      t.index %i[discussion_topic_id set_id set_type],
              unique: true,
              where: "discussion_topic_id IS NOT NULL AND workflow_state = 'active' AND set_id IS NOT NULL",
              name: "index_assignment_overrides_on_discussion_topic_id_and_set"
      t.index %i[attachment_id set_id set_type],
              unique: true,
              where: "attachment_id IS NOT NULL AND workflow_state = 'active' AND set_id IS NOT NULL",
              name: "index_assignment_overrides_on_attachment_id_and_set"
    end

    create_table :assignment_override_students do |t|
      t.timestamps precision: nil
      t.references :assignment, foreign_key: true
      t.references :assignment_override, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: { deferrable: :immediate }
      t.references :quiz, foreign_key: true
      t.string :workflow_state, default: "active", null: false, index: true
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.references :context_module, foreign_key: true, index: false
      t.references :wiki_page, foreign_key: true, index: { where: "wiki_page_id IS NOT NULL" }
      t.references :discussion_topic, foreign_key: true, index: { where: "discussion_topic_id IS NOT NULL" }
      t.references :attachment, foreign_key: true, index: { where: "attachment_id IS NOT NULL" }

      t.index [:assignment_id, :user_id], unique: true, where: "workflow_state = 'active'"
      t.index [:user_id, :quiz_id]
      t.index [:context_module_id, :user_id],
              where: "context_module_id IS NOT NULL",
              unique: true,
              name: "index_assignment_override_students_on_context_module_and_user"
      t.index [:wiki_page_id, :user_id],
              unique: true,
              where: "wiki_page_id IS NOT NULL AND workflow_state = 'active'",
              name: "index_aos_on_active_wiki_page_and_user"
      t.index [:discussion_topic_id, :user_id],
              unique: true,
              where: "discussion_topic_id IS NOT NULL AND workflow_state = 'active'",
              name: "index_aos_on_active_discussion_topic_and_user"
      t.index [:attachment_id, :user_id],
              unique: true,
              where: "attachment_id IS NOT NULL AND workflow_state = 'active'",
              name: "index_aos_on_active_attachment_and_user"
    end

    create_table :attachments do |t|
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.bigint :size
      t.references :folder, index: false
      t.string :content_type, limit: 255
      t.text :filename
      t.string :uuid, limit: 255
      t.text :display_name
      t.timestamps null: true, precision: nil
      t.string :workflow_state, limit: 255
      t.references :user
      t.boolean :locked, default: false
      t.string :file_state, limit: 255
      t.timestamp :deleted_at
      t.integer :position
      t.timestamp :lock_at
      t.timestamp :unlock_at
      t.boolean :could_be_locked
      t.references :root_attachment,
                   foreign_key: { to_table: :attachments },
                   index: { where: "root_attachment_id IS NOT NULL", name: "index_attachments_on_root_attachment_id_not_null" }
      t.references :cloned_item
      t.string :migration_id, limit: 255
      t.string :namespace, limit: 255, index: true
      t.string :media_entry_id, limit: 255, index: true
      t.string :md5, limit: 255
      t.string :encoding, limit: 255
      t.boolean :need_notify, index: { where: "need_notify" }
      t.text :upload_error_message
      t.references :replacement_attachment,
                   foreign_key: { to_table: :attachments },
                   index: { where: "replacement_attachment_id IS NOT NULL" }
      t.references :usage_rights, foreign_key: true, index: { where: "usage_rights_id IS NOT NULL" }
      t.timestamp :modified_at
      t.timestamp :viewed_at
      t.string :instfs_uuid, index: { where: "instfs_uuid IS NOT NULL" }
      t.references :root_account
      t.string :category, default: "uncategorized", null: false
      t.integer :word_count
      t.string :visibility_level, limit: 32, default: "inherit", null: false
      t.boolean :only_visible_to_overrides, null: false, default: false

      t.index [:context_id, :context_type]
      t.index [:md5, :namespace]
      t.index [:workflow_state, :updated_at]
      t.index %i[folder_id file_state position]
      t.index [:folder_id, :position], where: "folder_id IS NOT NULL"
      t.index %i[context_id context_type migration_id],
              where: "migration_id IS NOT NULL",
              name: "index_attachments_on_context_and_migration_id"
      t.index %i[md5 namespace content_type],
              where: "root_attachment_id IS NULL and filename IS NOT NULL"
      t.index %i[context_id context_type migration_id],
              opclass: { migration_id: :text_pattern_ops },
              where: "migration_id IS NOT NULL",
              name: "index_attachments_on_context_and_migration_id_pattern_ops"
      t.index :created_at, where: "context_type IN ('ContentExport', 'ContentMigration') and file_state NOT IN ('deleted', 'broken') and root_attachment_id is null"
      t.index :context_type, where: "workflow_state = 'deleted' and file_state = 'deleted'"
      t.index "folder_id, file_state, (display_name COLLATE public.\"und-u-kn-true\")",
              name: :index_attachments_on_folder_id_and_file_state_and_display_name,
              where: "folder_id IS NOT NULL"
    end

    execute(<<~SQL) # rubocop:disable Rails/SquishedSQLHeredocs
      CREATE FUNCTION #{connection.quote_table_name("attachment_before_insert_verify_active_folder__tr_fn")} () RETURNS trigger AS $$
      DECLARE
        folder_state text;
      BEGIN
        SELECT workflow_state INTO folder_state FROM folders WHERE folders.id = NEW.folder_id FOR SHARE;
        if folder_state = 'deleted' then
          RAISE EXCEPTION 'Cannot create attachments in deleted folders --> %', NEW.folder_id;
        end if;
        RETURN NEW;
      END;
      $$ LANGUAGE plpgsql;
    SQL
    set_search_path("attachment_before_insert_verify_active_folder__tr_fn")

    execute(<<~SQL.squish)
      CREATE TRIGGER attachment_before_insert_verify_active_folder__tr
        BEFORE INSERT ON #{Attachment.quoted_table_name}
        FOR EACH ROW
        EXECUTE PROCEDURE #{connection.quote_table_name("attachment_before_insert_verify_active_folder__tr_fn")}()
    SQL

    create_enum :enum_attachment_associations_field_name, ["syllabus_body"]
    create_table :attachment_associations do |t|
      t.references :attachment
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.references :root_account
      t.timestamps null: false, default: -> { "now()" }
      t.references :user, foreign_key: true
      t.enum :field_name, enum_type: "#{Shard.current.name}.enum_attachment_associations_field_name"

      t.index [:context_id, :context_type], name: "attachment_associations_a_id_a_type"
    end

    create_table :attachment_upload_statuses do |t|
      t.references :attachment, null: false, foreign_key: true
      t.text :error, null: false
      t.timestamp :created_at, null: false
    end

    create_table :auditor_authentication_records do |t|
      t.string :uuid, null: false, index: { unique: true, name: "index_auth_audits_on_unique_uuid" }
      t.references :account, null: false, foreign_key: true
      t.string :event_type, null: false
      t.references :pseudonym, null: false, foreign_key: true
      t.string :request_id, null: false
      t.references :user, null: false, foreign_key: true
      t.timestamp :created_at, null: false
    end

    create_table :auditor_course_records do |t|
      t.string :uuid, null: false, index: { unique: true, name: "index_course_audits_on_unique_uuid" }
      t.references :account, null: false, foreign_key: true
      t.references :course, null: false, foreign_key: true
      t.text :data
      t.string :event_source, null: false
      t.string :event_type, null: false
      t.string :request_id, null: false
      t.references :sis_batch
      t.references :user, foreign_key: true
      t.timestamp :created_at, null: false
    end

    create_table :auditor_feature_flag_records do |t|
      t.string :uuid, null: false, index: true
      t.references :feature_flag, null: false
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }
      t.references :context, polymorphic: true, index: false
      t.string :feature_name
      t.string :event_type, null: false
      t.string :state_before, null: false
      t.string :state_after, null: false
      t.string :request_id, null: false
      t.references :user, foreign_key: true
      t.timestamp :created_at, null: false
    end

    create_table :auditor_grade_change_records do |t|
      t.string :uuid, null: false, index: { unique: true, name: "index_grade_audits_on_unique_uuid" }
      t.references :account, null: false, foreign_key: true
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.references :assignment, foreign_key: true
      t.bigint :context_id, null: false
      t.string :context_type, null: false
      t.string :event_type, null: false
      t.boolean :excused_after, null: false
      t.boolean :excused_before, null: false
      t.string :grade_after
      t.string :grade_before
      t.boolean :graded_anonymously
      t.references :grader, foreign_key: { to_table: :users }
      t.float :points_possible_after
      t.float :points_possible_before
      t.string :request_id, null: false
      t.float :score_after
      t.float :score_before
      t.references :student, null: false, foreign_key: { to_table: :users }
      t.references :submission, foreign_key: true
      t.integer :submission_version_number
      t.timestamp :created_at, null: false
      t.references :grading_period, foreign_key: true, index: { where: "grading_period_id IS NOT NULL" }

      t.index %i[context_type context_id assignment_id], name: "index_auditor_grades_by_course_and_assignment"
      t.index [:root_account_id, :grader_id], name: "index_auditor_grades_by_account_and_grader"
      t.index [:root_account_id, :student_id], name: "index_auditor_grades_by_account_and_student"
      t.index %i[context_type context_id grader_id], name: "index_auditor_grades_by_course_and_grader"
      t.index %i[context_type context_id student_id], name: "index_auditor_grades_by_course_and_student"
    end

    create_table :auditor_pseudonym_records do |t|
      t.references :pseudonym, null: false, foreign_key: true
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }
      t.references :performing_user, null: false, index: false
      t.string :action, null: false
      t.string :hostname, null: false
      t.string :pid, null: false
      t.string :uuid, null: false, index: true
      t.string :event_type, null: false
      t.string :request_id

      t.timestamp :created_at, null: false
    end

    create_table :authentication_providers do |t|
      t.references :account, null: false, foreign_key: true
      t.integer :auth_port
      t.string :auth_host, limit: 255
      t.string :auth_base, limit: 255
      t.string :auth_username, limit: 255
      t.string :auth_crypted_password, limit: 2048
      t.string :auth_password_salt, limit: 255
      t.string :auth_type, limit: 255
      t.string :auth_over_tls, limit: 255, default: "start_tls"
      t.timestamps precision: nil
      t.string :log_in_url, limit: 255
      t.string :log_out_url, limit: 255
      t.string :identifier_format, limit: 255
      t.text :certificate_fingerprint
      t.string :entity_id, limit: 255
      t.text :auth_filter
      t.string :requested_authn_context, limit: 255
      t.timestamp :last_timeout_failure
      t.text :login_attribute
      t.string :idp_entity_id, limit: 255
      t.integer :position
      t.boolean :parent_registration, default: false, null: false
      t.string :workflow_state, default: "active", null: false, limit: 255, index: true
      t.boolean :jit_provisioning, default: false, null: false
      t.string :metadata_uri, limit: 255, index: { where: "metadata_uri IS NOT NULL" }
      t.json :settings, default: {}, null: false
      t.text :internal_ca
      # this field will be removed after VERIFY_NONE is removed entirely
      t.boolean :verify_tls_cert_opt_in, default: false, null: false
    end

    create_table :blackout_dates do |t|
      t.references :context, polymorphic: true, index: { name: "index_blackout_dates_on_context_type_and_context_id" }, null: false
      t.date :start_date, :end_date, null: false
      t.string :event_title, limit: 255, null: false
      t.timestamps precision: 6
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false

      t.replica_identity_index
    end

    create_table :block_editors do |t|
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.references :context, polymorphic: true, null: false
      t.bigint :time
      t.jsonb :blocks, null: false
      t.string :editor_version
      t.timestamps

      t.replica_identity_index
    end

    create_table :block_editor_templates do |t|
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.references :context, polymorphic: true, null: false
      t.string :name, null: false, limit: 255
      t.string :description, limit: 255
      t.string :tags, array: true, default: [], null: false
      t.jsonb :node_tree, null: false
      t.string :editor_version, null: false, limit: 255
      t.string :template_type, null: false, limit: 255
      t.text :thumbnail
      t.string :workflow_state, null: false, limit: 255
      t.timestamps

      t.replica_identity_index
      t.index %i[id root_account_id template_type], unique: true, name: "index_block_editor_templates_on_id_and_type"
    end

    create_table :bookmarks_bookmarks do |t|
      t.references :user, null: false, foreign_key: true
      t.text :name, null: false
      t.text :url, null: false
      t.integer :position
      t.text :json
    end

    create_table :brand_configs, id: false do |t|
      t.primary_keys [:md5]

      t.string :md5, limit: 32, null: false, unique: true
      t.text :variables
      t.boolean :share, default: false, null: false, index: true
      t.string :name, limit: 255
      t.timestamp :created_at, null: false
      t.text :js_overrides
      t.text :css_overrides
      t.text :mobile_js_overrides
      t.text :mobile_css_overrides
      t.string :parent_md5, limit: 255
    end

    create_table :calendar_events do |t|
      t.string :title, limit: 255
      t.text :description, limit: 16_777_215
      t.text :location_name
      t.text :location_address
      t.timestamp :start_at, index: { where: "workflow_state<>'deleted'" }
      t.timestamp :end_at
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.references :user, foreign_key: true
      t.boolean :all_day
      t.date :all_day_date
      t.timestamp :deleted_at
      t.references :cloned_item, foreign_key: true, index: { where: "cloned_item_id IS NOT NULL" }
      t.string :context_code, limit: 255, index: true
      t.string :migration_id, limit: 255
      t.string :time_zone_edited, limit: 255
      t.references :parent_calendar_event, foreign_key: { to_table: :calendar_events }
      t.string :effective_context_code, limit: 255, index: { where: "effective_context_code IS NOT NULL" }
      t.integer :participants_per_appointment
      t.boolean :override_participants_per_appointment
      t.text :comments
      t.string :timetable_code, limit: 255
      t.references :web_conference, foreign_key: true, index: { where: "web_conference_id IS NOT NULL" }
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.boolean :important_dates, default: false, null: false, index: { where: "important_dates" }
      t.string :rrule, limit: 255
      t.uuid :series_uuid, index: true
      t.boolean :series_head
      t.boolean :blackout_date, default: false, null: false

      t.index [:context_id, :context_type]
      t.index %i[context_id context_type timetable_code], where: "timetable_code IS NOT NULL", unique: true, name: "index_calendar_events_on_context_and_timetable_code"
    end

    create_table :canvadocs do |t|
      t.string :document_id, limit: 255, index: { unique: true }
      t.string :process_state, limit: 255
      t.references :attachment, null: false, foreign_key: true
      t.timestamps precision: nil
      t.boolean :has_annotations
    end

    create_table :canvadocs_annotation_contexts do |t|
      t.references :attachment, foreign_key: true, index: false, null: false
      t.references :submission, null: false, foreign_key: true
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }
      t.string :launch_id, null: false
      t.integer :submission_attempt
      t.timestamps precision: nil

      t.index %i[attachment_id submission_attempt submission_id],
              name: "index_attachment_attempt_submission",
              unique: true
      t.index [:attachment_id, :submission_id],
              where: "submission_attempt IS NULL",
              name: "index_attachment_submission",
              unique: true
    end

    create_table :canvadocs_submissions do |t|
      t.references :canvadoc
      t.references :crocodoc_document, index: { where: "crocodoc_document_id IS NOT NULL" }
      t.references :submission, null: false

      t.index [:submission_id, :canvadoc_id],
              where: "canvadoc_id IS NOT NULL",
              name: "unique_submissions_and_canvadocs",
              unique: true
      t.index [:submission_id, :crocodoc_document_id],
              where: "crocodoc_document_id IS NOT NULL",
              name: "unique_submissions_and_crocodocs",
              unique: true
    end

    create_table :canvas_metadata do |t|
      t.string :key, null: false, index: { unique: true }
      t.jsonb :payload, null: false
      t.timestamps precision: nil
    end

    create_table :collaborations do |t|
      t.string :collaboration_type, limit: 255
      t.string :document_id, limit: 255
      t.references :user, foreign_key: true
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.string :url, limit: 255
      t.string :uuid, limit: 255
      t.text :data
      t.timestamps precision: nil
      t.text :description
      t.string :title, null: false, limit: 255
      t.string :workflow_state, default: "active", null: false, limit: 255
      t.timestamp :deleted_at
      t.string :context_code, limit: 255
      t.string :type, limit: 255
      t.uuid :resource_link_lookup_uuid
      t.references :root_account, foreign_key: { to_table: :accounts }

      t.index [:context_id, :context_type]
    end

    create_table :collaborators do |t|
      t.references :user, foreign_key: true
      t.references :collaboration, foreign_key: true
      t.timestamps precision: nil
      t.string :authorized_service_user_id, limit: 255
      t.references :group, foreign_key: true
    end

    create_table :comment_bank_items do |t|
      t.references :course, null: false, foreign_key: true
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.references :user, null: false, foreign_key: true
      t.text :comment, null: false
      t.timestamps precision: 6
      t.string :workflow_state, null: false, default: "active"

      t.index :user_id,
              where: "workflow_state <> 'deleted'",
              name: "index_active_comment_bank_items_on_user"
      t.replica_identity_index
    end

    create_table :communication_channels do |t|
      t.string :path, null: false, limit: 255
      t.string :path_type, default: "email", null: false, limit: 255
      t.integer :position
      t.references :user, null: false, foreign_key: true, index: false
      t.references :pseudonym, index: false
      t.integer :bounce_count, default: 0
      t.string :workflow_state, null: false, limit: 255
      t.string :confirmation_code, limit: 255, index: true
      t.timestamps precision: nil
      t.boolean :build_pseudonym_on_confirm
      t.timestamp :last_bounce_at, index: { where: "bounce_count > 0" }
      # last_bounce_details was originally intended to have limit: 32768, but
      # it was typoed as "length" instead of "limit" so it did not apply
      t.text :last_bounce_details
      t.timestamp :last_suppression_bounce_at
      t.timestamp :last_transient_bounce_at
      # last_transient_bounce_details was originally intended to have limit:
      # 32768, but it was typoed as "length" instead of "limit" so it did not apply
      t.text :last_transient_bounce_details
      t.timestamp :confirmation_code_expires_at
      t.integer :confirmation_sent_count, default: 0, null: false
      t.bigint :root_account_ids, array: true
      t.string :confirmation_redirect

      t.index [:pseudonym_id, :position]
      t.index [:user_id, :position]
      t.index "LOWER(path), path_type", name: "index_communication_channels_on_path_and_path_type"
      if (trgm = connection.extension(:pg_trgm)&.schema)
        t.index "lower(path) #{trgm}.gin_trgm_ops", name: "index_gin_trgm_communication_channels_path", using: :gin
        t.index "user_id, LOWER(path), path_type",
                unique: true,
                name: "index_communication_channels_on_user_id_and_path_and_path_type"
      end
    end

    create_table :conditional_release_rules do |t|
      t.references :course, foreign_key: true
      t.references :trigger_assignment, foreign_key: { to_table: :assignments }
      t.timestamp :deleted_at
      t.references :root_account,
                   foreign_key: { to_table: :accounts },
                   null: false,
                   index: { name: "index_cr_rules_on_root_account_id" }
      t.timestamps precision: nil

      t.index [:root_account_id, :course_id], where: "deleted_at IS NULL", name: "index_cr_rules_on_account_and_course"
    end

    create_table :conditional_release_scoring_ranges do |t|
      t.references :rule,
                   foreign_key: { to_table: :conditional_release_rules },
                   index: { where: "deleted_at IS NULL", name: "index_cr_scoring_ranges_on_rule_id" },
                   null: false
      t.decimal :lower_bound
      t.decimal :upper_bound
      t.integer :position
      t.timestamp :deleted_at
      t.references :root_account,
                   foreign_key: { to_table: :accounts },
                   null: false,
                   index: { name: "index_cr_scoring_ranges_on_root_account_id" }
      t.timestamps precision: nil

      t.index :rule_id
    end

    create_table :conditional_release_assignment_sets do |t|
      t.references :scoring_range,
                   foreign_key: { to_table: :conditional_release_scoring_ranges },
                   index: { where: "deleted_at IS NULL", name: "index_cr_assignment_sets_on_scoring_range_id" },
                   null: false
      t.integer :position
      t.timestamp :deleted_at
      t.references :root_account,
                   foreign_key: { to_table: :accounts },
                   null: false,
                   index: { name: "index_cr_assignment_sets_on_root_account_id" }
      t.timestamps precision: nil

      t.index :scoring_range_id
    end

    create_table :conditional_release_assignment_set_associations do |t|
      t.references :assignment_set,
                   foreign_key: { to_table: :conditional_release_assignment_sets },
                   index: { name: "index_crasa_on_assignment_set_id", where: "assignment_set_id IS NOT NULL" }
      t.references :assignment,
                   foreign_key: true,
                   index: { where: "deleted_at IS NULL", name: "index_cr_assignment_set_associations_on_set" }
      t.integer :position
      t.timestamp :deleted_at
      t.references :root_account,
                   foreign_key: { to_table: :accounts },
                   null: false,
                   index: { name: "index_cr_assignment_set_associations_on_root_account_id" }
      t.timestamps precision: nil

      t.index [:assignment_id, :assignment_set_id],
              unique: true,
              where: "deleted_at IS NULL",
              name: "index_cr_assignment_set_associations_on_assignment_and_set"
      t.index :assignment_id, name: "index_crasa_on_assignment_id", where: "assignment_id IS NOT NULL"
    end

    create_table :conditional_release_assignment_set_actions do |t|
      t.string :action, null: false
      t.string :source, null: false
      t.references :student, null: false, index: false
      t.references :actor, null: false, index: false
      t.references :assignment_set, index: false
      t.timestamp :deleted_at
      t.references :root_account,
                   foreign_key: { to_table: :accounts },
                   null: false,
                   index: { name: "index_cr_assignment_set_actions_on_root_account_id" }
      t.timestamps precision: nil

      t.index :assignment_set_id,
              where: "deleted_at IS NULL",
              name: "index_cr_assignment_set_actions_on_assignment_set_id"
      t.index %i[assignment_set_id student_id created_at],
              order: { created_at: :desc },
              where: "deleted_at IS NULL",
              name: "index_cr_assignment_set_actions_on_set_and_student"
    end

    create_table :content_exports do |t|
      t.references :user, foreign_key: true, index: { where: "user_id IS NOT NULL" }
      t.references :attachment, foreign_key: true
      t.string :export_type, limit: 255
      t.text :settings
      t.float :progress
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.references :content_migration
      t.references :context, polymorphic: { limit: 255 }, index: false
      t.boolean :global_identifiers, default: false, null: false

      t.index [:context_id, :context_type]
    end

    create_table :content_migrations do |t|
      t.bigint :context_id, null: false
      t.references :user, foreign_key: true, index: { where: "user_id IS NOT NULL" }
      t.string :workflow_state, null: false, limit: 255
      t.text :migration_settings
      t.timestamp :started_at
      t.timestamp :finished_at
      t.timestamps precision: nil
      t.float :progress
      t.string :context_type, limit: 255
      t.references :attachment, index: { where: "attachment_id IS NOT NULL" }
      t.references :overview_attachment,
                   foreign_key: { to_table: :attachments },
                   index: { where: "overview_attachment_id IS NOT NULL" }
      t.references :exported_attachment,
                   foreign_key: { to_table: :attachments },
                   index: { where: "exported_attachment_id IS NOT NULL" }
      t.references :source_course, index: { where: "source_course_id IS NOT NULL" }
      t.string :migration_type, limit: 255
      t.references :child_subscription,
                   foreign_key: { to_table: :master_courses_child_subscriptions },
                   index: { where: "child_subscription_id IS NOT NULL" }
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.references :asset_map_attachment, index: { where: "asset_map_attachment_id IS NOT NULL" }, foreign_key: { to_table: :attachments }

      t.index [:context_id, :id], name: "index_content_migrations_on_context_id_and_id_no_clause"
      t.index [:context_id, :id], where: "workflow_state='queued'"
      t.index [:context_id, :started_at],
              name: "index_content_migrations_blocked_migrations",
              where: "started_at IS NOT NULL"
    end

    create_table :content_participations do |t|
      t.references :content, polymorphic: { limit: 255 }, null: false, index: false
      t.references :user, null: false, foreign_key: true
      t.string :workflow_state, null: false, limit: 255
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.string :content_item, null: false, default: "grade"
      t.timestamps null: false, default: -> { "now()" }

      t.index %i[content_id content_type user_id content_item],
              name: "index_content_participations_by_type_uniquely",
              unique: true
      t.index :user_id,
              name: "index_content_participations_on_user_id_unread",
              where: "workflow_state = 'unread'"
    end

    create_table :content_participation_counts do |t|
      t.string :content_type, limit: 255
      t.references :context, polymorphic: { limit: 255 }, index: false
      t.references :user, index: { where: "user_id IS NOT NULL" }
      t.integer :unread_count, default: 0
      t.timestamps precision: nil
      t.references :root_account, foreign_key: { to_table: :accounts }

      t.index %i[context_id context_type user_id content_type], name: "index_content_participation_counts_uniquely", unique: true
    end

    create_table :content_shares do |t|
      t.text :name, null: false
      t.timestamps precision: nil
      t.references :user, null: false, foreign_key: true, index: false
      t.references :content_export, null: false
      t.references :sender, foreign_key: { to_table: :users }, index: { where: "sender_id IS NOT NULL" }
      t.string :read_state, limit: 255, null: false
      t.string :type, limit: 255, null: false
      t.references :root_account

      t.index %i[user_id content_export_id sender_id],
              unique: true,
              name: "index_content_shares_on_user_and_content_export_and_sender_ids"
    end

    create_table :content_tags do |t|
      t.bigint :content_id
      t.string :content_type, limit: 255
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.text :title
      t.string :tag, limit: 255
      t.text :url
      t.timestamps precision: nil
      t.text :comments
      t.string :tag_type, default: "default", limit: 255
      t.references :context_module, foreign_key: true
      t.integer :position
      t.integer :indent
      t.string :migration_id, limit: 255
      t.references :learning_outcome, foreign_key: true, index: { where: "learning_outcome_id IS NOT NULL" }
      t.string :context_code, limit: 255
      t.float :mastery_score
      t.references :rubric_association, index: false
      t.string :workflow_state, default: "active", null: false, limit: 255
      t.references :cloned_item, foreign_key: true, index: { where: "cloned_item_id IS NOT NULL" }
      t.bigint :associated_asset_id
      t.string :associated_asset_type, limit: 255
      t.boolean :new_tab
      t.jsonb :link_settings
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.jsonb :external_data

      t.replica_identity_index
      t.index [:content_id, :content_type]
      t.index [:context_id, :context_type]
      t.index [:associated_asset_id, :associated_asset_type], name: "index_content_tags_on_associated_asset"
      t.index %i[context_id context_type content_type],
              where: "workflow_state = 'active'",
              name: "index_content_tags_on_context_when_active"
      t.index %i[content_type context_type context_id],
              where: "workflow_state<>'deleted'",
              name: "index_content_tags_for_due_date_cacher"
      t.index [:context_id, :content_id],
              where: "context_type = 'Account'",
              name: "index_content_tags_on_context_id_and_content_id_for_accounts"
    end

    create_table :context_external_tools do |t|
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.string :domain, limit: 255
      t.string :url, limit: 4.kilobytes
      t.text :shared_secret, null: false
      t.text :consumer_key, null: false, index: true
      t.string :name, null: false, limit: 255
      t.text :description
      t.text :settings
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.string :migration_id, limit: 255
      t.references :cloned_item, foreign_key: true, index: { where: "cloned_item_id IS NOT NULL" }
      t.string :tool_id, limit: 255, index: true
      t.boolean :not_selectable, default: false, null: false
      t.string :app_center_id, limit: 255
      t.boolean :allow_membership_service_access, default: false, null: false
      t.references :developer_key
      t.references :root_account, null: false, index: false
      t.boolean :is_rce_favorite, default: false, null: false
      t.string :identity_hash, limit: 64, index: { where: "identity_hash <> 'duplicate'" }
      t.text :lti_version, null: false, limit: 8, default: "1.1"
      t.string :unified_tool_id, limit: 255
      t.boolean :unified_tool_id_needs_update, default: false, null: false
      t.timestamp :unified_tool_id_last_updated_at, precision: 6
      t.references :lti_registration, index: { where: "lti_registration_id IS NOT NULL" }
      t.boolean :asset_processor_eula_required

      t.replica_identity_index
      t.index [:context_id, :context_type]
      t.index %i[context_id context_type migration_id], where: "migration_id IS NOT NULL", name: "index_external_tools_on_context_and_migration_id"
    end

    create_table :context_external_tool_placements do |t|
      t.string :placement_type, limit: 255
      t.references :context_external_tool, null: false, foreign_key: true, index: { name: "external_tool_placements_tool_id" }

      t.index [:placement_type, :context_external_tool_id], unique: true, name: "external_tool_placements_type_and_tool_id"
    end

    create_table :context_modules do |t|
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.text :name
      t.integer :position
      t.text :prerequisites
      t.text :completion_requirements
      t.timestamps precision: nil
      t.string :workflow_state, default: "active", null: false, limit: 255
      t.timestamp :deleted_at
      t.timestamp :unlock_at
      t.string :migration_id, limit: 255
      t.boolean :require_sequential_progress
      t.references :cloned_item, foreign_key: true, index: { where: "cloned_item_id IS NOT NULL" }
      t.text :completion_events
      t.integer :requirement_count
      t.references :root_account, foreign_key: { to_table: :accounts }

      t.index [:context_id, :context_type]
    end

    create_table :context_module_progressions do |t|
      t.references :context_module, foreign_key: true
      t.references :user, foreign_key: true, index: false
      t.text :requirements_met
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.boolean :collapsed
      t.integer :current_position
      t.timestamp :completed_at
      t.boolean :current
      t.integer :lock_version, default: 0, null: false
      t.timestamp :evaluated_at
      t.text :incomplete_requirements
      t.references :root_account, foreign_key: { to_table: :accounts }

      t.index [:user_id, :context_module_id], unique: true, name: "index_cmp_on_user_id_and_module_id"
    end

    create_table :conversations do |t|
      # for quick lookups so we know whether or not we need to create a new one
      t.string :private_hash, limit: 255, index: { unique: true }
      t.boolean :has_attachments, default: false, null: false
      t.boolean :has_media_objects, default: false, null: false
      t.text :tags
      t.text :root_account_ids
      t.string :subject, limit: 255
      t.references :context, polymorphic: { limit: 255 }, index: false
      t.timestamp :updated_at
    end

    create_table :conversation_batches do |t|
      t.string :workflow_state, null: false, limit: 255
      t.references :user, null: false, foreign_key: true, index: false
      t.text :recipient_ids
      t.references :root_conversation_message, null: false, foreign_key: { to_table: :conversation_messages }
      t.text :conversation_message_ids
      t.text :tags
      t.timestamps precision: nil
      t.references :context, polymorphic: { limit: 255 }, index: false
      t.string :subject, limit: 255
      t.boolean :group

      t.index [:user_id, :workflow_state]
    end

    create_table :conversation_messages do |t|
      t.references :conversation, foreign_key: true, index: false
      t.references :author
      t.timestamp :created_at
      t.boolean :generated
      t.text :body
      t.text :forwarded_message_ids
      t.string :media_comment_id, limit: 255
      t.string :media_comment_type, limit: 255
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.bigint :asset_id
      t.string :asset_type, limit: 255
      t.text :attachment_ids
      t.boolean :has_attachments
      t.boolean :has_media_objects
      t.text :root_account_ids
      t.boolean :automated, default: false, null: false
      t.text :inbox_settings_ooo_hash
      t.timestamp :updated_at, null: false, precision: 6, default: -> { "now()" }

      t.index [:conversation_id, :created_at]
    end

    create_table :conversation_message_participants do |t|
      t.references :conversation_message,
                   foreign_key: true,
                   index: { name: "index_conversation_message_participants_on_message_id" }
      t.references :conversation_participant, index: false
      t.text :tags
      t.references :user, index: false
      t.string :workflow_state, limit: 255
      t.timestamp :deleted_at, index: true
      t.text :root_account_ids
      t.timestamps null: false, default: -> { "now()" }

      t.index [:conversation_participant_id, :conversation_message_id], name: "index_cmp_on_cpi_and_cmi"
      t.index [:user_id, :conversation_message_id], name: "index_conversation_message_participants_on_uid_and_message_id", unique: true
    end

    create_table :conversation_participants do |t|
      t.references :conversation, null: false, index: false
      t.references :user, null: false, index: {
        where: "workflow_state = 'unread'",
        name: "index_conversation_participants_unread_on_user_id"
      }
      t.timestamp :last_message_at
      t.boolean :subscribed, default: true
      t.string :workflow_state, null: false, limit: 255
      t.timestamp :last_authored_at
      t.boolean :has_attachments, default: false, null: false
      t.boolean :has_media_objects, default: false, null: false
      t.integer :message_count, default: 0
      t.string :label, limit: 255
      t.text :tags
      t.timestamp :visible_last_authored_at
      t.text :root_account_ids
      t.string :private_hash, limit: 255
      t.timestamp :updated_at

      t.index [:user_id, :last_message_at]
      t.index [:conversation_id, :user_id], unique: true
      t.index [:private_hash, :user_id], where: "private_hash IS NOT NULL", unique: true
    end

    create_table :course_account_associations do |t|
      t.references :course, null: false, foreign_key: true, index: false
      t.references :account, null: false, foreign_key: true, index: false
      t.integer :depth, null: false
      t.timestamps precision: nil
      t.references :course_section, foreign_key: true
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.replica_identity_index
      t.index [:account_id, :depth], name: "index_course_account_associations_on_account_id_and_depth_id"
      t.index %i[course_id course_section_id account_id], unique: true, name: "index_caa_on_course_id_and_section_id_and_account_id"
    end

    create_table :course_paces do |t|
      t.references :course, null: false, foreign_key: true
      t.references :course_section, index: false
      t.references :user, index: false
      t.string :workflow_state, default: "unpublished", null: false, limit: 255
      t.date :end_date
      t.boolean :exclude_weekends, null: false, default: true
      t.boolean :hard_end_dates, null: false, default: false
      t.timestamps precision: 6
      t.timestamp :published_at
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.string :migration_id
      t.string :selected_days_to_skip, array: true, default: %w[sat sun], null: false
      t.text :assignments_weighting
      t.integer :time_to_complete_calendar_days

      t.replica_identity_index
      t.index :course_id, unique: true, where: "course_section_id IS NULL AND user_id IS NULL AND workflow_state='active'", name: "course_paces_unique_primary_plan_index"
      t.index :course_section_id, unique: true, where: "workflow_state='active'"
      t.index [:course_id, :user_id], unique: true, where: "workflow_state='active'"
    end

    create_table :course_pace_module_items do |t|
      t.references :course_pace, foreign_key: true
      t.integer :duration, null: false, default: 0
      t.references :module_item, foreign_key: { to_table: :content_tags }
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.timestamps precision: 6
      t.string :migration_id

      t.replica_identity_index
    end

    create_table :course_score_statistics do |t|
      t.references :course, null: false, foreign_key: true, index: { unique: true }
      t.decimal :average, precision: 8, scale: 2, null: false
      t.integer :score_count, null: false
      t.timestamps precision: nil
    end

    create_table :course_sections do |t|
      t.string :sis_source_id, limit: 255
      t.references :sis_batch, foreign_key: true, index: { where: "sis_batch_id IS NOT NULL" }
      t.references :course, null: false, foreign_key: true
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.references :enrollment_term, foreign_key: true
      t.string :name, null: false, limit: 255
      t.boolean :default_section
      t.boolean :accepting_enrollments
      t.boolean :can_manually_enroll
      t.timestamp :start_at
      t.timestamp :end_at
      t.timestamps precision: nil
      t.string :workflow_state, default: "active", null: false, limit: 255
      t.boolean :restrict_enrollments_to_section_dates
      t.references :nonxlist_course,
                   foreign_key: { to_table: :courses },
                   index: { where: "nonxlist_course_id IS NOT NULL",
                            name: "index_course_sections_on_nonxlist_course" }
      t.text :stuck_sis_fields
      t.string :integration_id, limit: 255

      t.replica_identity_index
      t.index [:sis_source_id, :root_account_id], where: "sis_source_id IS NOT NULL", unique: true
      t.index [:integration_id, :root_account_id],
              unique: true,
              name: "index_sections_on_integration_id",
              where: "integration_id IS NOT NULL"
      t.index :course_id,
              unique: true,
              where: "default_section = 't' AND workflow_state <> 'deleted'",
              name: "index_course_sections_unique_default_section"
    end

    create_table :crocodoc_documents do |t|
      t.string :uuid, limit: 255, index: true
      t.string :process_state, limit: 255, index: true
      t.references :attachment
      t.timestamps null: true, precision: nil
    end

    create_table :csp_domains do |t|
      t.references :account, null: false, foreign_key: true, index: false
      t.string :domain, null: false, limit: 255
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil

      t.index [:account_id, :domain], unique: true
      t.index [:account_id, :workflow_state]
    end

    create_table :custom_data do |t|
      t.text :data
      t.string :namespace, limit: 255
      t.references :user, index: false
      t.timestamps precision: nil

      t.index [:user_id, :namespace], unique: true
    end

    create_table :custom_grade_statuses do |t|
      t.string :color, limit: 7, null: false
      t.string :name, null: false, limit: 14
      t.string :workflow_state, null: false, default: "active", limit: 255
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.references :created_by, null: false, foreign_key: { to_table: :users }
      t.references :deleted_by, foreign_key: { to_table: :users }
      t.timestamps precision: 6
      t.boolean :applies_to_submissions, null: false, default: true
      t.boolean :applies_to_final_grade, null: false, default: true
      t.boolean :allow_final_grade_value, null: false, default: true

      t.replica_identity_index
    end

    create_table :custom_gradebook_columns do |t|
      t.string :title, null: false, limit: 255
      t.integer :position, null: false
      t.string :workflow_state, default: "active", null: false, limit: 255
      t.references :course, null: false, foreign_key: {  dependent: true }
      t.timestamps precision: nil
      t.boolean :teacher_notes, default: false, null: false
      t.boolean :read_only, default: false, null: false
      t.references :root_account, foreign_key: { to_table: :accounts }
    end

    create_table :custom_gradebook_column_data do |t|
      t.string :content, null: false, limit: 255
      t.references :user, null: false, foreign_key: true
      t.references :custom_gradebook_column, null: false, foreign_key: true, index: false
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.timestamps null: false, default: -> { "now()" }

      t.index [:custom_gradebook_column_id, :user_id],
              unique: true,
              name: "index_custom_gradebook_column_data_unique_column_and_user"
    end

    create_table :delayed_messages do |t|
      t.references :notification, index: false
      t.references :notification_policy, foreign_key: true
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.references :communication_channel, foreign_key: true, index: false
      t.string :frequency, limit: 255
      t.string :workflow_state, limit: 255
      t.timestamp :batched_at
      t.timestamps null: true, precision: nil
      t.timestamp :send_at, index: { name: "by_sent_at" }
      t.text :link
      t.text :name_of_topic
      t.text :summary
      t.references :root_account, index: false
      t.references :notification_policy_override,
                   foreign_key: true,
                   index: { where: "notification_policy_override_id IS NOT NULL" }

      t.index [:workflow_state, :send_at], name: "ws_sa"
      t.index %i[communication_channel_id root_account_id workflow_state send_at], name: "ccid_raid_ws_sa"
      t.index :send_at, where: "workflow_state = 'pending'", name: "index_delayed_messages_pending"
    end

    create_table :delayed_notifications do |t|
      t.references :notification, null: false, index: false
      t.bigint :asset_id, null: false
      t.string :asset_type, null: false, limit: 255
      t.text :recipient_keys
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
    end

    create_table :developer_keys do |t|
      t.string :api_key, limit: 255
      t.string :email, limit: 255
      t.string :user_name, limit: 255
      t.references :account, index: false
      t.timestamps precision: nil
      t.references :user, index: false
      t.string :name, limit: 255
      t.string :redirect_uri, limit: 255
      t.string :icon_url, limit: 255
      t.string :sns_arn, limit: 255
      t.boolean :trusted
      t.boolean :force_token_reuse
      t.string :workflow_state, default: "active", null: false, limit: 255
      t.boolean :replace_tokens
      t.boolean :auto_expire_tokens, default: false, null: false
      t.string :redirect_uris, array: true, default: [], null: false, limit: 4096
      t.text :notes
      t.integer :access_token_count, default: 0, null: false
      t.string :vendor_code, index: true
      t.boolean :visible, default: false, null: false
      t.text :scopes
      t.boolean :require_scopes, default: false, null: false
      t.boolean :test_cluster_only, default: false, null: false
      t.jsonb :public_jwk
      t.boolean :internal_service, default: false, null: false
      t.text :oidc_initiation_url
      t.string :public_jwk_url
      t.boolean :is_lti_key, default: false, null: false
      t.boolean :allow_includes, default: false, null: false
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.string :client_credentials_audience
      t.references :service_user, foreign_key: { to_table: :users }, index: { where: "service_user_id IS NOT NULL" }
      t.references :lti_registration,
                   index: { where: "lti_registration_id IS NOT NULL" },
                   foreign_key: { to_table: :lti_registrations },
                   null: true
      t.string :client_type, null: false, default: "confidential"
      t.string :allowed_audiences, array: true, default: [], null: false, limit: 4096

      t.replica_identity_index
    end

    create_table :developer_key_account_bindings do |t|
      t.references :account, null: false, foreign_key: true, index: false
      t.references :developer_key, null: false
      t.string :workflow_state, null: false, default: "off"
      t.timestamps precision: nil
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.replica_identity_index
      t.index %i[account_id developer_key_id], name: "index_dev_key_bindings_on_account_id_and_developer_key_id", unique: true
    end

    create_table :discussion_entries do |t|
      t.text :message
      t.references :discussion_topic, foreign_key: true, index: false
      t.references :user, foreign_key: true
      t.references :parent, foreign_key: { to_table: :discussion_entries }
      t.timestamps precision: nil
      t.references :attachment, index: false
      t.string :workflow_state, default: "active", limit: 255
      t.timestamp :deleted_at
      t.string :migration_id, limit: 255
      t.references :editor, foreign_key: { to_table: :users }, index: { where: "editor_id IS NOT NULL" }
      t.references :root_entry, foreign_key: { to_table: :discussion_entries }, index: false
      t.integer :depth
      t.integer :rating_count
      t.integer :rating_sum
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.boolean :legacy, default: true, null: false
      t.boolean :include_reply_preview, default: false, null: false
      t.boolean :is_anonymous_author, default: false, null: false
      t.references :quoted_entry, foreign_key: { to_table: :discussion_entries }
      t.datetime :edited_at, precision: 6

      t.replica_identity_index
      t.index %i[root_entry_id workflow_state created_at], name: "index_discussion_entries_root_entry"
      t.index %i[discussion_topic_id updated_at created_at], name: "index_discussion_entries_for_topic"
      t.index [:user_id, :discussion_topic_id],
              where: "workflow_state <> 'deleted'",
              name: "index_discussion_entries_active_on_user_id_and_topic"
    end

    create_table :discussion_entry_drafts do |t|
      t.references :discussion_topic, null: false, foreign_key: true
      t.references :discussion_entry, foreign_key: true, index: false
      t.references :root_entry, foreign_key: { to_table: :discussion_entries }
      t.references :parent, foreign_key: { to_table: :discussion_entries }
      t.references :attachment, foreign_key: true
      t.references :user, null: false, foreign_key: true
      t.text :message
      t.boolean :include_reply_preview, null: false, default: false
      t.timestamps precision: 6

      t.index %i[discussion_topic_id user_id],
              name: "unique_index_on_topic_and_user",
              where: "discussion_entry_id IS NULL AND root_entry_id IS NULL",
              unique: true
      t.index %i[root_entry_id user_id],
              name: "unique_index_on_root_entry_and_user",
              where: "discussion_entry_id IS NULL",
              unique: true
      t.index %i[discussion_entry_id user_id],
              name: "unique_index_on_entry_and_user",
              unique: true
    end

    create_table :discussion_entry_participants do |t|
      t.references :discussion_entry, null: false, foreign_key: true, index: false
      t.references :user, null: false, foreign_key: true
      t.string :workflow_state, null: false, limit: 255
      t.boolean :forced_read_state
      t.integer :rating
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.string :report_type, limit: 255
      t.timestamp :read_at
      t.timestamps null: false, default: -> { "now()" }

      t.replica_identity_index
      t.index [:discussion_entry_id, :user_id], name: "index_entry_participant_on_entry_id_and_user_id", unique: true
    end

    create_table :discussion_entry_versions do |t|
      t.references :discussion_entry, null: false, foreign_key: true
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.references :user, foreign_key: true
      t.bigint :version
      t.text :message
      t.timestamps precision: 6

      t.replica_identity_index
    end

    create_table :discussion_topics do |t|
      t.string :title, limit: 255
      t.text :message, limit: 16_777_215
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.string :type, limit: 255
      t.references :user, foreign_key: true
      t.string :workflow_state, null: false, limit: 255, index: true
      t.timestamp :last_reply_at
      t.timestamps precision: nil
      t.timestamp :delayed_post_at
      t.timestamp :posted_at
      t.references :assignment, foreign_key: true
      t.references :attachment, foreign_key: true, index: { where: "attachment_id IS NOT NULL" }
      t.timestamp :deleted_at
      t.references :root_topic, foreign_key: { to_table: :discussion_topics }
      t.boolean :could_be_locked, default: false, null: false
      t.references :cloned_item, foreign_key: true, index: { where: "cloned_item_id IS NOT NULL" }
      t.string :context_code, limit: 255
      t.integer :position
      t.string :migration_id, limit: 255
      t.references :old_assignment, foreign_key: { to_table: :assignments }, index: { where: "old_assignment_id IS NOT NULL" }
      t.timestamp :subtopics_refreshed_at
      t.references :last_assignment, index: false
      t.references :external_feed, foreign_key: true, index: { where: "external_feed_id IS NOT NULL" }
      t.references :editor, foreign_key: { to_table: :users }, index: { where: "editor_id IS NOT NULL" }
      t.boolean :podcast_enabled, default: false, null: false
      t.boolean :podcast_has_student_posts, default: false, null: false
      t.boolean :require_initial_post, default: false, null: false
      t.string :discussion_type, limit: 255
      t.timestamp :lock_at
      t.boolean :pinned, default: false, null: false
      t.boolean :locked, default: false, null: false
      t.references :group_category, foreign_key: true, index: { where: "group_category_id IS NOT NULL" }
      t.boolean :allow_rating, default: false, null: false
      t.boolean :only_graders_can_rate, default: false, null: false
      t.boolean :sort_by_rating, default: false, null: false
      t.timestamp :todo_date
      t.boolean :is_section_specific, default: false, null: false
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.string :anonymous_state, limit: 255
      t.boolean :is_anonymous_author, default: false, null: false
      t.integer :reply_to_entry_required_count, null: false, default: 0
      t.timestamp :unlock_at, precision: 6
      t.boolean :only_visible_to_overrides, null: false, default: false
      t.boolean :summary_enabled, default: false, null: false
      t.timestamp :edited_at, precision: 6
      t.string :sort_order, null: false, default: DiscussionTopic::SortOrder::DESC
      t.boolean :sort_order_locked, null: false, default: false
      t.boolean :expanded, null: false, default: false
      t.boolean :expanded_locked, null: false, default: false

      t.replica_identity_index
      t.index [:context_id, :position]
      t.index [:id, :type]
      t.index %i[context_id context_type root_topic_id], unique: true, name: "index_discussion_topics_unique_subtopic_per_context"
      t.index [:context_id, :last_reply_at], name: "index_discussion_topics_on_context_and_last_reply_at"
      if (trgm = connection.extension(:pg_trgm)&.schema)
        t.index "LOWER(title) #{trgm}.gin_trgm_ops", name: "index_gin_trgm_discussion_topics_title", using: :gin
      end
    end

    create_table :discussion_topic_insights do |t|
      t.references :discussion_topic, foreign_key: true, index: false, null: false
      t.references :user, foreign_key: true, null: false
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.string :workflow_state, null: false, default: "created"
      t.timestamps

      t.replica_identity_index
      t.index %i[discussion_topic_id created_at], order: { created_at: :desc }, name: "index_discussion_topic_insights_latest"
    end

    create_table :discussion_topic_insight_entries do |t|
      t.references :discussion_topic_insight, foreign_key: true, null: false, index: { name: "idx_on_discussion_topic_insight_id_d7375ce0ba" }
      t.references :discussion_topic, foreign_key: true, index: false, null: false
      t.references :discussion_entry, foreign_key: true, null: false
      t.references :discussion_entry_version, foreign_key: true, null: false, index: { name: "idx_on_discussion_entry_version_id_7c6cf01a1b" }
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.string :locale, null: false, limit: 255
      t.string :dynamic_content_hash, null: false, limit: 64

      t.jsonb :ai_evaluation, null: false, default: {}

      t.references :ai_evaluation_human_reviewer, foreign_key: { to_table: :users }, index: { name: "idx_on_ai_evaluation_human_reviewer_id_51e74ff825" }
      t.boolean :ai_evaluation_human_feedback_liked, null: false, default: false
      t.boolean :ai_evaluation_human_feedback_disliked, null: false, default: false
      t.string :ai_evaluation_human_feedback_notes, null: false, default: "", limit: 1024

      t.timestamps

      t.replica_identity_index
      t.index %i[discussion_topic_id discussion_entry_id dynamic_content_hash], unique: true, name: "index_discussion_topic_insight_entries_lookup"
      t.index %i[discussion_topic_id locale discussion_entry_id created_at], order: { created_at: :desc }, name: "index_discussion_topic_insight_entries_latest"
    end

    create_table :discussion_topic_materialized_views, id: false do |t|
      t.primary_keys [:discussion_topic_id]

      t.references :discussion_topic, null: false, foreign_key: true, index: false
      t.text :json_structure
      t.text :participants_array
      t.text :entry_ids_array

      t.timestamps precision: nil
      t.timestamp :generation_started_at
    end

    create_table :discussion_topic_participants do |t|
      t.references :discussion_topic, null: false, foreign_key: true, index: false
      t.references :user, null: false, foreign_key: true
      t.integer :unread_entry_count, default: 0, null: false
      t.string :workflow_state, null: false, limit: 255
      t.boolean :subscribed
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.timestamps null: false, default: -> { "now()" }
      t.string :sort_order, null: false, default: DiscussionTopic::SortOrder::INHERIT
      t.boolean :expanded
      t.boolean :summary_enabled, null: false, default: false

      t.replica_identity_index
      t.index [:discussion_topic_id, :user_id], name: "index_topic_participant_on_topic_id_and_user_id", unique: true
    end

    create_table :discussion_topic_section_visibilities do |t|
      t.references :discussion_topic, null: false, foreign_key: true, index: { name: "idx_discussion_topic_section_visibility_on_topic" }
      t.references :course_section, null: false, foreign_key: true, index: { name: "idx_discussion_topic_section_visibility_on_section" }
      t.timestamps precision: nil
      t.string :workflow_state, null: false, limit: 255
    end

    create_table :discussion_topic_summaries do |t|
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.replica_identity_index
      t.references :discussion_topic, null: false, foreign_key: true, index: { name: "index_summaries_on_topic_id" }
      t.string :llm_config_version, null: false, limit: 255
      t.string :dynamic_content_hash, null: false, limit: 255
      t.timestamps
      t.text :summary
      t.integer :input_tokens
      t.integer :output_tokens
      t.float :generation_time
      t.references :parent, foreign_key: { to_table: :discussion_topic_summaries }
      t.string :locale
      t.references :user, foreign_key: true
      t.string :user_input, limit: 255

      t.index %i[discussion_topic_id
                 llm_config_version
                 dynamic_content_hash
                 parent_id
                 created_at],
              name: "index_summaries_for_lookup",
              order: { created_at: :desc }
    end

    create_table :discussion_topic_summary_feedback do |t|
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.replica_identity_index
      t.references :discussion_topic_summary, null: false, foreign_key: true, index: false
      t.references :user, null: false, foreign_key: true
      t.boolean :liked, default: false, null: false
      t.boolean :disliked, default: false, null: false
      t.boolean :summary_disabled, null: false, default: false
      t.timestamps

      t.index %i[discussion_topic_summary_id user_id], unique: true, name: "index_feedback_on_summary_id_and_user_id"

      t.check_constraint "NOT (liked AND disliked)", name: "chk_liked_disliked_disjunction"
    end

    create_table :enrollment_dates_overrides do |t|
      t.references :enrollment_term, foreign_key: true
      t.string :enrollment_type, limit: 255
      t.bigint :context_id, null: false
      t.string :context_type, limit: 255
      t.timestamp :start_at
      t.timestamp :end_at
      t.timestamps precision: nil
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.text :stuck_sis_fields

      t.replica_identity_index :context_id
    end

    create_table :enrollment_states, id: false do |t|
      t.primary_keys [:enrollment_id]

      t.references :enrollment, null: false, foreign_key: true, index: false
      t.string :state, limit: 255, index: true
      t.boolean :state_is_current, null: false, default: false
      t.timestamp :state_started_at
      t.timestamp :state_valid_until, index: true
      t.boolean :restricted_access, null: false, default: false
      t.boolean :access_is_current, null: false, default: false
      t.integer :lock_version, default: 0, null: false
      t.timestamp :updated_at
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.replica_identity_index
      t.index [:state_is_current, :access_is_current], name: "index_enrollment_states_on_currents"
    end

    create_table :enrollment_terms do |t|
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.string :name, limit: 255
      t.string :term_code, limit: 255
      t.string :sis_source_id, limit: 255
      t.references :sis_batch, foreign_key: true, index: { where: "sis_batch_id IS NOT NULL" }
      t.timestamp :start_at
      t.timestamp :end_at
      t.boolean :accepting_enrollments
      t.boolean :can_manually_enroll
      t.timestamps precision: nil
      t.string :workflow_state, default: "active", null: false, limit: 255
      t.text :stuck_sis_fields
      t.string :integration_id, limit: 255
      t.references :grading_period_group, foreign_key: true

      t.replica_identity_index
      t.index [:sis_source_id, :root_account_id], where: "sis_source_id IS NOT NULL", unique: true
      t.index [:integration_id, :root_account_id],
              unique: true,
              name: "index_terms_on_integration_id",
              where: "integration_id IS NOT NULL"
    end

    create_table :enrollments do |t|
      t.references :user, null: false, foreign_key: true
      t.references :course,
                   foreign_key: true,
                   null: false,
                   index: { where: "workflow_state = 'active'", name: "index_enrollments_on_course_when_active" }
      t.string :type, null: false, limit: 255
      t.string :uuid, limit: 255, index: true
      t.string :workflow_state, null: false, limit: 255, index: true
      t.timestamps precision: nil
      t.references :associated_user, foreign_key: { to_table: :users }, index: { where: "associated_user_id IS NOT NULL" }
      t.references :sis_batch, foreign_key: true, index: { where: "sis_batch_id IS NOT NULL" }
      t.timestamp :start_at
      t.timestamp :end_at
      t.references :course_section, null: false, foreign_key: true, index: false
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.timestamp :completed_at
      t.boolean :self_enrolled
      t.string :grade_publishing_status, default: "unpublished", limit: 255
      t.timestamp :last_publish_attempt_at
      t.text :stuck_sis_fields
      t.text :grade_publishing_message
      t.boolean :limit_privileges_to_course_section, default: false, null: false
      t.timestamp :last_activity_at
      t.integer :total_activity_time
      t.references :role, null: false, foreign_key: true, index: false
      t.references :sis_pseudonym
      t.timestamp :last_attended_at
      t.references :temporary_enrollment_source_user, foreign_key: { to_table: :users }, index: false
      t.references :temporary_enrollment_pairing, foreign_key: true, index: { where: "temporary_enrollment_pairing_id IS NOT NULL" }
      t.datetime :archived_at

      t.replica_identity_index
      t.index [:course_id, :workflow_state]
      t.index [:root_account_id, :course_id]
      t.index %i[user_id type role_id course_section_id associated_user_id],
              where: "associated_user_id IS NOT NULL",
              name: "index_enrollments_on_user_type_role_section_associated_user",
              unique: true
      t.index %i[user_id type role_id course_section_id],
              where: "associated_user_id IS NULL ",
              name: "index_enrollments_on_user_type_role_section",
              unique: true
      t.index [:course_id, :user_id]
      t.index [:role_id, :user_id]
      t.index [:course_section_id, :id]
      t.index [:course_id, :id]
      t.index %i[temporary_enrollment_source_user_id user_id type role_id course_section_id],
              where: "temporary_enrollment_source_user_id IS NOT NULL",
              name: "index_enrollments_on_temp_enrollment_user_type_role_section",
              unique: true
      t.index [:user_id, :course_section_id],
              name: "index_on_user_id_and_course_section_id"
    end

    create_table :eportfolios do |t|
      t.references :user, null: false, foreign_key: true
      t.string :name, limit: 255
      t.boolean :public
      t.timestamps precision: nil
      t.string :uuid, limit: 255
      t.string :workflow_state, default: "active", null: false, limit: 255
      t.timestamp :deleted_at
      t.string :spam_status, index: true
    end

    create_table :eportfolio_categories do |t|
      t.references :eportfolio, null: false, foreign_key: true
      t.string :name, limit: 255
      t.integer :position
      t.string :slug, limit: 255
      t.timestamps precision: nil
    end

    create_table :eportfolio_entries do |t|
      t.references :eportfolio, null: false, foreign_key: true
      t.references :eportfolio_category, null: false, foreign_key: true
      t.integer :position
      t.string :name, limit: 255
      t.boolean :allow_comments
      t.boolean :show_comments
      t.string :slug, limit: 255
      t.text :content, limit: 16_777_215
      t.timestamps precision: nil
    end

    create_table :epub_exports do |t|
      t.references :content_export, :course, :user, foreign_key: true
      t.string :workflow_state, default: "created", limit: 255
      t.timestamps precision: nil
      t.string :type, limit: 255
    end

    create_table :error_reports do |t|
      t.text :backtrace
      t.text :url
      t.text :message
      t.text :comments
      t.references :user, index: { where: "user_id IS NOT NULL" }
      t.timestamps null: true, precision: nil
      t.string :email, limit: 255
      t.boolean :during_tests, default: false
      t.text :user_agent
      t.string :request_method, limit: 255
      t.text :http_env, limit: 16_777_215
      t.text :subject
      t.string :request_context_id, limit: 255
      t.references :account, index: false
      t.bigint :zendesk_ticket_id, index: true
      t.text :data
      t.string :category, limit: 255, index: true

      t.index :created_at, name: "error_reports_created_at"
    end

    create_table :estimated_durations do |t|
      t.interval :duration
      t.references :discussion_topic, foreign_key: true, index: { where: "discussion_topic_id IS NOT NULL", unique: true }
      t.references :assignment, foreign_key: true, index: { where: "assignment_id IS NOT NULL", unique: true }
      t.references :attachment, foreign_key: true, index: { where: "attachment_id IS NOT NULL", unique: true }
      t.references :quiz, foreign_key: true, index: { where: "quiz_id IS NOT NULL", unique: true }
      t.references :wiki_page, foreign_key: true, index: { where: "wiki_page_id IS NOT NULL", unique: true }
      t.references :content_tag, foreign_key: true, index: { where: "content_tag_id IS NOT NULL", unique: true }
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.timestamps

      t.check_constraint <<~SQL.squish, name: "check_that_exactly_one_foreign_key_is_present"
        (
          (discussion_topic_id IS NOT NULL)::int +
          (assignment_id IS NOT NULL)::int +
          (attachment_id IS NOT NULL)::int +
          (quiz_id IS NOT NULL)::int +
          (wiki_page_id IS NOT NULL)::int +
          (content_tag_id IS NOT NULL)::int
        ) = 1
      SQL

      t.replica_identity_index
    end

    create_table :event_stream_failures do |t|
      t.string :operation, null: false, limit: 255
      t.string :event_stream, null: false, limit: 255
      t.string :record_id, null: false, limit: 255
      t.text :payload, null: false
      t.text :exception
      t.text :backtrace
      t.timestamps precision: nil
    end

    create_table :external_feeds do |t|
      t.references :user, foreign_key: true, index: { where: "user_id IS NOT NULL" }
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.integer :consecutive_failures
      t.integer :failures
      t.timestamp :refresh_at
      t.string :title, limit: 255
      t.string :url, null: false, limit: 255
      t.string :header_match, limit: 255
      t.timestamps precision: nil
      t.string :verbosity, limit: 255
      t.string :migration_id, limit: 255

      t.index [:context_id, :context_type]
      t.index %i[context_id context_type url verbosity], unique: true, where: "header_match IS NULL", name: "index_external_feeds_uniquely_1"
      t.index %i[context_id context_type url header_match verbosity], unique: true, where: "header_match IS NOT NULL", name: "index_external_feeds_uniquely_2"
    end

    create_table :external_feed_entries do |t|
      t.references :user, foreign_key: true, index: { where: "user_id IS NOT NULL" }
      t.references :external_feed, null: false, foreign_key: true
      t.text :title
      t.text :message
      t.string :source_name, limit: 255
      t.text :source_url
      t.timestamp :posted_at
      t.string :workflow_state, null: false, limit: 255
      t.text :url, index: true
      t.bigint :asset_id
      t.string :asset_type, limit: 255
      t.string :uuid, limit: 255, index: true
      t.timestamps precision: nil
    end

    create_table :external_integration_keys do |t|
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.string :key_value, null: false, limit: 255
      t.string :key_type, null: false, limit: 255
      t.timestamps precision: nil

      t.index %i[context_id context_type key_type], name: "index_external_integration_keys_unique", unique: true
    end

    create_table :favorites do |t|
      t.references :user, foreign_key: true, index: false
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.timestamps precision: nil
      t.references :root_account, index: false, null: false

      t.replica_identity_index
      t.index %i[user_id context_id context_type], unique: true, name: "index_favorites_unique_user_object"
    end

    create_table :feature_flags do |t|
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.string :feature, null: false, limit: 255
      t.string :state, default: "allowed", null: false, limit: 255
      t.timestamps precision: nil
      t.bigint :root_account_ids, array: true, default: [], null: false

      t.check_constraint "context_type IN ('Account', 'User', 'Course')",
                         name: "chk_context_type_enum"

      t.index %i[context_id context_type feature], unique: true, name: "index_feature_flags_on_context_and_feature"
    end

    create_table :folders do |t|
      t.string :name, limit: 255
      t.text :full_name
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.references :parent_folder, foreign_key: { to_table: :folders }
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.timestamp :deleted_at
      t.boolean :locked
      t.timestamp :lock_at
      t.timestamp :unlock_at
      t.references :cloned_item
      t.integer :position
      t.string :submission_context_code, limit: 255
      t.string :unique_type
      t.references :root_account, index: false, null: false

      t.replica_identity_index
      t.index [:context_id, :context_type]
      t.index [:context_id, :context_type], unique: true, name: "index_folders_on_context_id_and_context_type_for_root_folders", where: "parent_folder_id IS NULL AND workflow_state<>'deleted'"
      t.index [:submission_context_code, :parent_folder_id], unique: true
      t.index %i[unique_type context_id context_type],
              unique: true,
              where: "unique_type IS NOT NULL AND workflow_state <> 'deleted'"
    end

    execute(<<~SQL) # rubocop:disable Rails/SquishedSQLHeredocs
      CREATE FUNCTION #{connection.quote_table_name("folder_before_insert_verify_active_parent_folder__tr_fn")} () RETURNS trigger AS $$
      DECLARE
        parent_state text;
      BEGIN
        SELECT workflow_state INTO parent_state FROM folders WHERE folders.id = NEW.parent_folder_id FOR SHARE;
        if parent_state = 'deleted' then
          RAISE EXCEPTION 'Cannot create sub-folders in deleted folders --> %', NEW.parent_folder_id;
        end if;
        RETURN NEW;
      END;
      $$ LANGUAGE plpgsql;
    SQL
    set_search_path("folder_before_insert_verify_active_parent_folder__tr_fn")

    execute(<<~SQL.squish)
      CREATE TRIGGER folder_before_insert_verify_active_parent_folder__tr
        BEFORE INSERT ON #{Folder.quoted_table_name}
        FOR EACH ROW
        EXECUTE PROCEDURE #{connection.quote_table_name("folder_before_insert_verify_active_parent_folder__tr_fn")}()
    SQL

    create_table :gradebook_csvs do |t|
      t.references :user, null: false, foreign_key: true, index: false
      t.references :attachment, null: false, index: false
      t.references :progress, null: false, foreign_key: true
      t.references :course, null: false, foreign_key: true

      t.index [:user_id, :course_id]
    end

    create_table :gradebook_filters do |t|
      t.references :course, null: false, foreign_key: true, index: false
      t.references :user, null: false, foreign_key: true
      t.string :name, limit: 255, null: false
      t.jsonb :payload, null: false, default: {}
      t.timestamps precision: 6

      t.index [:course_id, :user_id]
    end

    create_table :gradebook_uploads do |t|
      t.timestamps precision: nil
      t.references :course, null: false, foreign_key: true, index: false
      t.references :user, null: false, foreign_key: true
      t.references :progress, null: false, foreign_key: true
      t.text :gradebook

      t.index [:course_id, :user_id], unique: true
    end

    create_table :grading_period_groups do |t|
      t.references :course, foreign_key: true
      t.references :account, foreign_key: true
      t.timestamps precision: nil
      t.string :workflow_state, default: "active", null: false, limit: 255, index: true
      t.string :title, limit: 255
      t.boolean :weighted
      t.boolean :display_totals_for_all_grading_periods, default: false, null: false
      t.references :root_account, foreign_key: { to_table: :accounts }, index: { where: "root_account_id IS NOT NULL" }
    end

    create_table :grading_periods do |t|
      t.float :weight
      t.timestamp :start_date, null: false
      t.timestamp :end_date, null: false
      t.timestamps precision: nil
      t.string :title, limit: 255
      t.string :workflow_state, default: "active", null: false, limit: 255, index: true
      t.references :grading_period_group, null: false, foreign_key: true
      t.timestamp :close_date
      t.references :root_account, foreign_key: { to_table: :accounts }
    end

    create_table :grading_standards do |t|
      t.string :title, limit: 255
      t.text :data
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.timestamps precision: nil
      t.references :user, foreign_key: true, index: { where: "user_id IS NOT NULL" }
      t.integer :usage_count
      t.string :context_code, limit: 255, index: true
      t.string :workflow_state, null: false, limit: 255
      t.string :migration_id, limit: 255
      t.integer :version
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.boolean :points_based, default: false, null: false
      t.decimal :scaling_factor, precision: 5, scale: 2, default: 1.0, null: false

      t.index [:context_id, :context_type]
    end

    create_table :group_categories do |t|
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.string :name, limit: 255
      t.string :role, limit: 255, index: true
      t.timestamp :deleted_at
      t.string :self_signup, limit: 255
      t.integer :group_limit
      t.string :auto_leader, limit: 255
      t.timestamps null: true, precision: nil
      t.string :sis_source_id
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.references :sis_batch, foreign_key: true
      t.boolean :non_collaborative, default: false, null: false
      t.datetime :self_signup_end_at

      t.replica_identity_index
      t.index [:context_id, :context_type], name: "index_group_categories_on_context"
      t.index [:root_account_id, :sis_source_id], where: "sis_source_id IS NOT NULL", unique: true
      t.index :non_collaborative
    end

    create_table :groups do |t|
      t.string :name, limit: 255
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.integer :max_membership
      t.boolean :is_public
      t.references :account, null: false, foreign_key: true
      t.references :wiki, foreign_key: true, index: { where: "wiki_id IS NOT NULL" }
      t.timestamp :deleted_at
      t.string :join_level, limit: 255
      t.string :default_view, default: "feed", limit: 255
      t.string :migration_id, limit: 255
      t.bigint :storage_quota
      t.string :uuid, null: false, limit: 255, index: { unique: true }
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.string :sis_source_id, limit: 255
      t.references :sis_batch, foreign_key: true, index: { where: "sis_batch_id IS NOT NULL" }
      t.text :stuck_sis_fields
      t.references :group_category, foreign_key: true
      t.text :description
      t.references :avatar_attachment, index: false
      t.references :leader, foreign_key: { to_table: :users }, index: { where: "leader_id IS NOT NULL" }
      t.string :lti_context_id, limit: 255
      t.boolean :non_collaborative, default: false, null: false

      t.replica_identity_index
      t.index [:context_id, :context_type]
      t.index [:sis_source_id, :root_account_id], where: "sis_source_id IS NOT NULL", unique: true
      t.index :non_collaborative
    end

    create_table :group_memberships do |t|
      t.references :group, null: false, foreign_key: true
      t.string :workflow_state, null: false, limit: 255, index: true
      t.timestamps precision: nil
      t.references :user, null: false, foreign_key: true
      t.string :uuid, null: false, limit: 255, index: { unique: true }
      t.references :sis_batch, foreign_key: true, index: { where: "sis_batch_id IS NOT NULL" }
      t.boolean :moderator
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.replica_identity_index
      t.index [:group_id, :user_id], unique: true, where: "workflow_state <> 'deleted'"
    end

    create_table :group_and_membership_importers do |t|
      t.references :group_category, null: false, foreign_key: true
      t.references :attachment, foreign_key: true, index: { where: "attachment_id IS NOT NULL" }
      t.string :workflow_state, null: false, default: "active"
      t.timestamps precision: nil
    end

    create_table :inbox_settings do |t|
      t.string :user_id, index: true, null: false
      t.boolean :use_signature, default: false, null: false
      t.string :signature, limit: 255
      t.boolean :use_out_of_office, default: false, null: false
      t.datetime :out_of_office_first_date
      t.datetime :out_of_office_last_date
      t.string :out_of_office_subject, limit: 255
      t.string :out_of_office_message, limit: 255
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.replica_identity_index
      t.timestamps
    end

    create_table :ignores do |t|
      t.references :asset, polymorphic: { limit: 255 }, null: false, index: false
      t.references :user, null: false, foreign_key: true
      t.string :purpose, null: false, limit: 255
      t.boolean :permanent, null: false, default: false
      t.timestamps precision: nil

      t.index %i[asset_id asset_type user_id purpose], unique: true, name: "index_ignores_on_asset_and_user_id_and_purpose"
    end

    create_table :late_policies do |t|
      t.references :course, null: false, foreign_key: true, index: { unique: true }
      t.boolean :missing_submission_deduction_enabled, null: false, default: false
      t.decimal :missing_submission_deduction, precision: 5, scale: 2, null: false, default: 100
      t.boolean :late_submission_deduction_enabled, null: false, default: false
      t.decimal :late_submission_deduction, precision: 5, scale: 2, null: false, default: 0
      t.string :late_submission_interval, limit: 16, null: false, default: "day"
      t.boolean :late_submission_minimum_percent_enabled, null: false, default: false
      t.decimal :late_submission_minimum_percent, precision: 5, scale: 2, null: false, default: 0
      t.timestamps precision: nil
      t.references :root_account, foreign_key: { to_table: :accounts }
    end

    create_table :learning_outcomes do |t|
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.string :short_description, null: false, limit: 255
      t.string :context_code, limit: 255
      t.text :description
      t.text :data
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.string :migration_id, limit: 255
      t.string :vendor_guid, limit: 255, index: true
      t.string :low_grade, limit: 255
      t.string :high_grade, limit: 255
      t.string :display_name, limit: 255
      t.string :calculation_method, limit: 255
      t.integer :calculation_int, limit: 2
      t.string :vendor_guid_2, limit: 255, index: true
      t.string :migration_id_2, limit: 255
      t.references :outcome_import, index: false
      t.bigint :root_account_ids, array: true, index: { using: :gin }
      t.references :copied_from_outcome, index: { where: "copied_from_outcome_id IS NOT NULL" }
      t.timestamp :archived_at, precision: 6, default: nil

      t.index [:context_id, :context_type]
    end

    create_table :learning_outcome_groups do |t|
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.string :title, null: false, limit: 255
      t.references :learning_outcome_group, foreign_key: true, index: { where: "learning_outcome_group_id IS NOT NULL" }
      t.references :root_learning_outcome_group,
                   foreign_key: { to_table: :learning_outcome_groups },
                   index: { where: "root_learning_outcome_group_id IS NOT NULL" }
      t.string :workflow_state, null: false, limit: 255
      t.text :description
      t.timestamps precision: nil
      t.string :migration_id, limit: 255
      t.string :vendor_guid, limit: 255, index: true
      t.string :low_grade, limit: 255
      t.string :high_grade, limit: 255
      t.string :vendor_guid_2, limit: 255, index: true
      t.string :migration_id_2, limit: 255
      t.references :outcome_import, index: false
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.references :source_outcome_group,
                   index: { where: "source_outcome_group_id IS NOT NULL" },
                   foreign_key: { to_table: :learning_outcome_groups }
      t.timestamp :archived_at, precision: 6, default: nil

      t.index [:context_id, :context_type]
      t.index %i[context_type context_id vendor_guid_2], name: "index_learning_outcome_groups_on_context_and_vendor_guid"
    end

    create_table :learning_outcome_question_results do |t|
      t.references :learning_outcome_result, index: { name: "index_LOQR_on_learning_outcome_result_id" }
      t.references :learning_outcome
      t.bigint :associated_asset_id
      t.string :associated_asset_type, limit: 255
      t.float :score
      t.float :possible
      t.boolean :mastery
      t.float :percent
      t.integer :attempt
      t.text :title
      t.float :original_score
      t.float :original_possible
      t.boolean :original_mastery
      t.timestamp :assessed_at
      t.timestamps precision: nil
      t.timestamp :submitted_at
      t.references :root_account, foreign_key: { to_table: :accounts }
    end

    create_table :learning_outcome_results do |t|
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.string :context_code, limit: 255
      t.bigint :association_id
      t.string :association_type, limit: 255
      t.references :content_tag, foreign_key: true
      t.references :learning_outcome, foreign_key: true, index: { where: "learning_outcome_id IS NOT NULL" }
      t.boolean :mastery
      t.references :user, foreign_key: true, index: false
      t.float :score
      t.timestamps precision: nil
      t.integer :attempt
      t.float :possible
      t.float :original_score
      t.float :original_possible
      t.boolean :original_mastery
      t.bigint :artifact_id
      t.string :artifact_type, limit: 255
      t.timestamp :assessed_at
      t.string :title, limit: 255
      t.float :percent
      t.bigint :associated_asset_id
      t.string :associated_asset_type, limit: 255
      t.timestamp :submitted_at
      t.boolean :hide_points, default: false, null: false
      t.boolean :hidden, default: false, null: false
      t.string :user_uuid, limit: 255
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.string :workflow_state, default: "active", null: false

      t.index %i[user_id content_tag_id association_id association_type associated_asset_id associated_asset_type],
              unique: true,
              name: "index_learning_outcome_results_association"
      t.index [:artifact_id, :artifact_type], name: "lor_artifact_id_idx"
    end

    create_table :live_assessments_assessments do |t|
      t.string :key, null: false, limit: 255
      t.string :title, null: false, limit: 255
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.timestamps precision: nil

      t.index %i[context_id context_type key], unique: true, name: "index_live_assessments"
    end

    create_table :live_assessments_results do |t|
      t.references :user, null: false
      t.references :assessor, null: false, foreign_key: { to_table: :users }
      t.references :assessment, null: false, foreign_key: { to_table: :live_assessments_assessments }, index: false
      t.boolean :passed, null: false
      t.timestamp :assessed_at, null: false

      t.index [:assessment_id, :user_id]
    end

    create_table :live_assessments_submissions do |t|
      t.references :user, null: false, foreign_key: true
      t.references :assessment, null: false, foreign_key: { to_table: :live_assessments_assessments }, index: false
      t.float :possible
      t.float :score
      t.timestamp :assessed_at
      t.timestamps precision: nil

      t.index [:assessment_id, :user_id], unique: true
    end

    create_table :lti_assets do |t|
      t.uuid :uuid, null: false, index: { unique: true }
      t.references :attachment, null: false
      t.references :submission, foreign_key: true
      t.string :workflow_state, limit: 255, null: false, default: "active"
      t.timestamps
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.string :sha256_checksum, limit: 64

      t.replica_identity_index
      t.index %i[attachment_id submission_id], unique: true, where: "submission_id IS NOT NULL"
    end

    create_table :lti_asset_processors do |t|
      t.string :url, limit: 4.kilobytes
      t.string :title, limit: 255
      t.string :text, limit: 255
      t.jsonb :custom
      t.jsonb :icon
      t.jsonb :window
      t.jsonb :iframe
      t.jsonb :report
      t.references :assignment, null: false, foreign_key: true
      t.references :context_external_tool, null: false, foreign_key: true
      t.string :workflow_state, limit: 255, null: false
      t.timestamps
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.replica_identity_index
    end

    create_table :lti_asset_processor_eula_acceptances do |t|
      t.references :user, null: false, foreign_key: true
      t.boolean :accepted, default: false, null: false
      t.timestamp :timestamp, null: false
      t.references :context_external_tool, null: false, foreign_key: true, index: { name: "idx_on_context_external_tool_id_236cb0425a" }

      t.string :workflow_state, limit: 255, null: false, default: "active"
      t.timestamps

      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.replica_identity_index
      t.index %i[user_id context_external_tool_id], where: "workflow_state = 'active'", name: "idx_on_user_id_context_external_tool_id_bee0622bb7"
    end

    create_table :lti_asset_reports do |t|
      t.string :report_type, null: false
      t.timestamp :timestamp, null: false
      t.string :title
      t.string :comment
      t.float :score_given
      t.float :score_maximum
      t.string :indication_color, limit: 255
      t.string :indication_alt, limit: 255
      t.string :processing_progress, null: false
      t.string :error_code
      t.integer :priority, null: false
      t.jsonb :extensions, null: false, default: {}
      t.references :lti_asset, null: false, foreign_key: true
      t.references :lti_asset_processor, null: false, foreign_key: true
      t.string :workflow_state, limit: 255, null: false
      t.timestamps
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.check_constraint <<~SQL.squish, name: "chk_score_maximum_present_if_score_given_present"
        (score_maximum IS NOT NULL) OR (score_given IS NULL)
      SQL

      t.replica_identity_index
      t.index %i[lti_asset_id lti_asset_processor_id report_type],
              unique: true,
              where: "workflow_state = 'active'",
              name: "idx_on_lti_asset_id_lti_asset_processor_id_report_t_a649bf83c5"
    end

    create_table :lti_ims_registrations do |t|
      t.jsonb :lti_tool_configuration, null: false
      t.references :developer_key, null: false, foreign_key: true
      t.text :redirect_uris, array: true, default: [], null: false
      t.text :initiate_login_uri, null: false
      t.string :client_name, null: false
      t.text :jwks_uri, null: false
      t.text :logo_uri
      t.string :contacts, array: true, default: [], null: false, limit: 255
      t.text :client_uri
      t.text :policy_uri
      t.text :tos_uri
      t.text :scopes, array: true, default: [], null: false
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.timestamps precision: 6
      t.string :guid
      t.jsonb :registration_overlay, default: {}
      t.references :lti_registration,
                   index: { where: "lti_registration_id IS NOT NULL" },
                   foreign_key: { to_table: :lti_registrations },
                   null: true
      t.string :workflow_state, limit: 255, default: "active"
      t.string :unified_tool_id, limit: 255
      t.string :registration_url, limit: 4096

      t.replica_identity_index
    end

    create_table :lti_line_items do |t|
      t.float :score_maximum, null: false
      t.string :label, null: false
      t.string :resource_id, index: true
      t.string :tag, index: true
      t.references :lti_resource_link, foreign_key: true
      t.references :assignment, null: false, foreign_key: true
      t.timestamps precision: nil
      t.bigint :client_id, null: false, index: true
      t.string :workflow_state, default: "active", null: false, index: true
      t.jsonb :extensions, default: {}
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.boolean :coupled, default: true, null: false
      t.timestamp :end_date_time
      t.datetime :start_date_time

      t.replica_identity_index
    end

    create_table :lti_links do |t|
      t.string :resource_link_id, null: false, index: { unique: true }
      t.string :vendor_code, null: false
      t.string :product_code, null: false
      t.string :resource_type_code, null: false
      t.bigint :linkable_id
      t.string :linkable_type
      t.text :custom_parameters
      t.text :resource_url
      t.timestamps precision: nil

      t.index [:linkable_id, :linkable_type]
    end

    create_table :lti_message_handlers do |t|
      t.string :message_type, null: false, limit: 255
      t.string :launch_path, null: false, limit: 255
      t.text :capabilities
      t.text :parameters
      t.references :resource_handler,
                   null: false,
                   foreign_key: { to_table: :lti_resource_handlers },
                   index: false
      t.timestamps precision: nil
      t.references :tool_proxy, foreign_key: { to_table: :lti_tool_proxies }

      t.index [:resource_handler_id, :message_type],
              name: "index_lti_message_handlers_on_resource_handler_and_type",
              unique: true
    end

    create_table :lti_notice_handlers do |t|
      t.belongs_to :account, null: false, index: false, foreign_key: true
      t.string :notice_type, null: false, limit: 255
      t.string :url, null: false, limit: 4.kilobytes
      t.string :workflow_state, null: false, default: "active", limit: 255
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.references :context_external_tool, null: false, foreign_key: true
      t.timestamps
      t.integer :max_batch_size

      t.replica_identity_index
      t.index %i[account_id context_external_tool_id], name: "idx_on_account_id_context_external_tool_id_6c0590594f"
    end

    create_table :lti_overlays do |t|
      t.belongs_to :registration, null: false
      t.belongs_to :account, null: false, index: false, foreign_key: true
      t.belongs_to :updated_by, foreign_key: { to_table: :users }
      t.string :workflow_state, null: false, default: "active", limit: 255
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.jsonb :data, null: false, default: {}
      t.timestamps

      t.replica_identity_index
      t.index %i[account_id registration_id], unique: true
    end

    create_table :lti_overlay_versions do |t|
      t.belongs_to :account, null: false, foreign_key: true
      t.belongs_to :lti_overlay, null: false, foreign_key: { to_table: :lti_overlays }
      t.belongs_to :created_by, null: false, foreign_key: { to_table: :users }
      t.jsonb :diff, null: false
      t.timestamps
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.boolean :caused_by_reset, default: false, null: false

      t.replica_identity_index
    end

    create_table :lti_product_families do |t|
      t.string :vendor_code, null: false, limit: 255
      t.string :product_code, null: false, limit: 255
      t.string :vendor_name, null: false, limit: 255
      t.text :vendor_description
      t.string :website, limit: 255
      t.string :vendor_email, limit: 255
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }
      t.timestamps precision: nil
      t.references :developer_key

      t.index %i[product_code vendor_code root_account_id developer_key_id],
              unique: true,
              name: "product_family_uniqueness"
    end

    create_table :lti_registrations, if_not_exists: true do |t|
      t.boolean :internal_service, null: false, default: false
      t.belongs_to :account, null: false, foreign_key: true
      t.belongs_to :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.belongs_to :created_by, foreign_key: { to_table: :users }, index: { if_not_exists: true }
      t.belongs_to :updated_by, foreign_key: { to_table: :users }, index: { if_not_exists: true }
      t.string :name, null: false, index: true, limit: 255
      t.string :admin_nickname, limit: 255
      t.string :vendor, limit: 255

      t.string :workflow_state, default: "active", null: false, limit: 255
      t.replica_identity_index

      t.timestamps
    end

    create_table :lti_registration_account_bindings do |t|
      t.belongs_to :registration, null: false
      t.belongs_to :account, null: false, foreign_key: true
      t.belongs_to :created_by, foreign_key: { to_table: :users }
      t.belongs_to :updated_by, foreign_key: { to_table: :users }
      t.string :workflow_state, null: false, default: "off"

      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.references :developer_key_account_binding, foreign_key: true, index: { name: "index_lrab_on_developer_key_account_binding_id" }
      t.replica_identity_index
      t.timestamps

      t.index %i[account_id registration_id], name: "index_lti_reg_bindings_on_account_id_and_registration_id", unique: true
    end

    create_table :lti_resource_handlers do |t|
      t.string :resource_type_code, null: false, limit: 255
      t.string :placements, limit: 255
      t.string :name, null: false, limit: 255
      t.text :description
      t.text :icon_info
      t.references :tool_proxy,
                   null: false,
                   foreign_key: { to_table: :lti_tool_proxies },
                   index: false
      t.timestamps precision: nil

      t.index [:tool_proxy_id, :resource_type_code],
              name: "index_lti_resource_handlers_on_tool_proxy_and_type_code",
              unique: true
    end

    create_table :lti_resource_links do |t|
      t.timestamps precision: nil
      t.references :context_external_tool, null: false, foreign_key: { to_table: :context_external_tools }
      t.string :workflow_state, default: "active", null: false, index: true
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.bigint :context_id, null: false
      t.string :context_type, limit: 255, null: false
      t.jsonb :custom
      t.uuid :lookup_uuid, null: false
      t.uuid :resource_link_uuid, null: false, index: { unique: true }
      t.string :url
      t.string :lti_1_1_id
      t.string :title

      t.replica_identity_index
      t.index [:context_id, :context_type], name: "index_lti_resource_links_by_context_id_context_type"
      t.index %i[lookup_uuid context_id context_type],
              unique: true,
              name: "index_lti_resource_links_unique_lookup_uuid_on_context"
    end

    create_table :lti_resource_placements do |t|
      t.string :placement, null: false, limit: 255
      t.timestamps precision: nil
      t.references :message_handler,
                   foreign_key: { to_table: :lti_message_handlers },
                   index: { where: "message_handler_id IS NOT NULL" }

      t.index [:placement, :message_handler_id],
              unique: true,
              where: "message_handler_id IS NOT NULL",
              name: "index_resource_placements_on_placement_and_message_handler"
    end

    create_table :lti_results do |t|
      t.float :result_score
      t.float :result_maximum
      t.text :comment
      t.string :activity_progress
      t.string :grading_progress
      t.references :lti_line_item, null: false, foreign_key: true
      t.references :submission, foreign_key: true
      t.references :user, null: false, foreign_key: true
      t.timestamps precision: nil
      t.string :workflow_state, default: "active", null: false, index: true
      t.jsonb :extensions, default: {}
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.replica_identity_index
      t.index %i[lti_line_item_id user_id], unique: true
    end

    create_table :lti_tool_configurations do |t|
      t.references :developer_key, null: false, foreign_key: true, index: { unique: true }
      t.timestamps precision: nil
      t.string :disabled_placements, array: true, default: []
      t.string :privacy_level
      t.string :unified_tool_id, limit: 255
      t.references :lti_registration, foreign_key: true, index: { where: "lti_registration_id IS NOT NULL" }
      t.string :title, limit: 255
      t.text :description, limit: 4_000
      t.string :target_link_uri, limit: 4_000
      t.string :domain, limit: 4_000
      t.string :tool_id, limit: 255
      t.string :public_jwk_url, limit: 4_000
      t.string :oidc_initiation_url, limit: 4_000
      t.jsonb :oidc_initiation_urls, default: {}, null: false
      t.jsonb :custom_fields, default: {}, null: false
      t.jsonb :launch_settings, default: {}, null: false
      t.jsonb :placements, default: [], null: false
      t.jsonb :public_jwk
      t.text :scopes, array: true, default: [], null: false
      t.text :redirect_uris, array: true, default: [], null: false
    end

    create_table :lti_tool_consumer_profiles do |t|
      t.text :services
      t.text :capabilities
      t.string :uuid, null: false, index: { unique: true }
      t.references :developer_key, null: false, foreign_key: true, index: { unique: true }
      t.timestamps precision: nil
    end

    create_table :lti_tool_proxies do |t|
      t.text :shared_secret, null: false
      t.string :guid, null: false, limit: 255, index: true
      t.string :product_version, null: false, limit: 255
      t.string :lti_version, null: false, limit: 255
      t.references :product_family, null: false, foreign_key: { to_table: :lti_product_families }
      t.bigint :context_id, null: false
      t.string :workflow_state, null: false, limit: 255
      t.text :raw_data, null: false
      t.timestamps precision: nil
      # NOTE: I think the original migration didn't want this to remain the
      # default, but they didn't remove it properly, so it still is.
      t.string :context_type, null: false, default: "Account", limit: 255
      t.string :name, limit: 255
      t.text :description
      t.text :update_payload
      t.text :registration_url
      t.string :subscription_id
    end

    create_table :lti_tool_proxy_bindings do |t|
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.references :tool_proxy, null: false, foreign_key: { to_table: :lti_tool_proxies }
      t.timestamps precision: nil
      t.boolean :enabled, null: false, default: true

      t.index %i[context_id context_type tool_proxy_id], name: "index_lti_tool_proxy_bindings_on_context_and_tool_proxy", unique: true
    end

    create_table :lti_tool_settings do |t|
      t.references :tool_proxy, index: false
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.text :resource_link_id
      t.text :custom
      t.timestamps precision: nil
      t.string :product_code
      t.string :vendor_code
      t.string :resource_type_code
      t.text :custom_parameters
      t.text :resource_url

      t.index %i[resource_link_id context_type context_id tool_proxy_id], name: "index_lti_tool_settings_on_link_context_and_tool_proxy", unique: true
    end

    create_table :master_courses_child_content_tags do |t|
      # mainly for bulk loading on import
      t.references :child_subscription,
                   null: false,
                   foreign_key: { to_table: :master_courses_child_subscriptions },
                   index: { name: "index_child_content_tags_on_subscription" }
      t.references :content,
                   polymorphic: { limit: 255 },
                   null: false,
                   index: { unique: true, name: "index_child_content_tags_on_content" }
      t.text :downstream_changes
      t.string :migration_id, index: { name: "index_child_content_tags_on_migration_id" }
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.timestamps null: false, default: -> { "now()" }

      t.index [:child_subscription_id, :migration_id],
              opclass: { migration_id: :text_pattern_ops },
              name: "index_mc_child_content_tags_on_sub_and_migration_id_pattern_ops"
    end

    create_table :master_courses_child_subscriptions do |t|
      t.references :master_template, null: false, foreign_key: { to_table: :master_courses_master_templates }
      # we may have to drop this foreign key at some point for cross-shard subscriptions
      t.references :child_course,
                   foreign_key: { to_table: :courses },
                   null: false,
                   index: { name: "index_child_subscriptions_on_child_course_id" }
      t.string :workflow_state, null: false, limit: 255
      # we can use this to keep track of which subscriptions are new
      # vs. which ones have been getting regular updates and we can use a selective copy for
      t.boolean :use_selective_copy, null: false, default: false
      t.timestamps precision: nil
      t.references :root_account, foreign_key: { to_table: :accounts }

      t.index [:master_template_id, :child_course_id],
              unique: true,
              where: "workflow_state <> 'deleted'",
              name: "index_mc_child_subscriptions_on_template_id_and_course_id"
    end

    create_table :master_courses_master_content_tags do |t|
      t.references :master_template, null: false, foreign_key: { to_table: :master_courses_master_templates }
      # should we add a workflow state and make this soft-deletable?
      # maybe someday if we decide to use these to define the template content aets
      t.references :content, polymorphic: { limit: 255 }, null: false, index: false
      # when we export an object for a master migration we'll set this column on the tag
      # when we update the content we'll erase this
      # so now we'll know what's been updated since the last successful export
      t.references :current_migration,
                   foreign_key: { to_table: :master_courses_master_migrations },
                   index: { where: "current_migration_id IS NOT NULL",
                            name: "index_master_content_tags_on_current_migration_id" }
      t.text :restrictions # we might not leave this at settings/content
      t.string :migration_id, index: { unique: true, name: "index_master_content_tags_on_migration_id" }
      t.boolean :use_default_restrictions, default: false, null: false
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.timestamps null: false, default: -> { "now()" }

      t.index %i[master_template_id content_type content_id],
              unique: true,
              name: "index_master_content_tags_on_template_id_and_content"
    end

    create_table :master_courses_master_migrations do |t|
      t.references :master_template, null: false, foreign_key: { to_table: :master_courses_master_templates }
      t.references :user, index: false # exports use a bunch of terrible user-dependent stuff
      # we can just use serialized columns here to store the rest of the data
      # instead of a million rows
      # since we won't really be needing any of it separately
      t.text :export_results # we can store the initial export details here
      t.timestamp :exports_started_at
      t.timestamp :imports_queued_at
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.timestamp :imports_completed_at
      t.text :comment
      t.boolean :send_notification, default: false, null: false
      t.text :migration_settings
      t.references :root_account, foreign_key: { to_table: :accounts }
    end

    create_table :master_courses_master_templates do |t|
      t.references :course, null: false, foreign_key: true
      t.boolean :full_course, null: false, default: true # we may not ever get around to allowing selective collection sets out but just in case
      t.string :workflow_state, limit: 255
      t.timestamps precision: nil
      # due to paranoia about race conditions around trying to make multiple migrations at once
      # we'll lock the template before we create the migration
      # and mark this column with the new migration unless there's already a currently running one, in which case we'll abort
      t.references :active_migration,
                   foreign_key: { to_table: :master_courses_master_migrations },
                   index: { where: "active_migration_id IS NOT NULL" }
      t.text :default_restrictions
      t.boolean :use_default_restrictions_by_type, default: false, null: false
      t.text :default_restrictions_by_type
      t.references :root_account, foreign_key: { to_table: :accounts }

      t.index :course_id,
              unique: true,
              where: "full_course AND workflow_state <> 'deleted'",
              name: "index_master_templates_unique_on_course_and_full"
    end

    create_table :master_courses_migration_results do |t|
      t.references :master_migration,
                   null: false,
                   foreign_key: { to_table: :master_courses_master_migrations },
                   index: false
      t.references :content_migration, null: false, foreign_key: true
      t.references :child_subscription, null: false, foreign_key: { to_table: :master_courses_child_subscriptions }
      t.string :import_type, null: false
      t.string :state, null: false
      t.text :results
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.timestamps null: false, default: -> { "now()" }

      t.index [:master_migration_id, :state],
              name: "index_mc_migration_results_on_master_mig_id_and_state"
      t.index [:master_migration_id, :content_migration_id],
              unique: true,
              name: "index_mc_migration_results_on_master_and_content_migration_ids"
    end

    create_table :media_objects do |t|
      t.references :user, foreign_key: true, index: { where: "user_id IS NOT NULL" }
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.string :workflow_state, null: false, limit: 255
      t.string :user_type, limit: 255
      t.string :title, limit: 255
      t.string :user_entered_title, limit: 255
      t.string :media_id, null: false, limit: 255, index: true
      t.string :media_type, limit: 255
      t.integer :duration
      t.integer :max_size
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.text :data
      t.timestamps precision: nil
      t.references :attachment
      t.integer :total_size
      t.string :old_media_id, limit: 255, index: true
      t.string :auto_caption_status, limit: 255
      t.string :auto_caption_media_id, limit: 255

      t.check_constraint "auto_caption_status IN ('complete', 'processing', 'failed_initial_validation', 'failed_handoff', 'failed_request', 'non_english_captions', 'failed_captions', 'failed_to_pull')",
                         name: "chk_auto_caption_status_enum"

      t.index [:context_id, :context_type]
    end

    create_table :media_tracks do |t|
      t.references :user, index: false
      t.references :media_object, null: false, index: false
      t.string :kind, default: "subtitles", limit: 255
      t.string :locale, default: "en", limit: 255
      t.text :content, null: false
      t.timestamps precision: nil
      t.text :webvtt_content
      t.references :attachment, index: false

      t.index [:media_object_id, :locale], name: "media_object_id_locale"
      t.index [:attachment_id, :locale], where: "attachment_id IS NOT NULL", unique: true
    end

    create_table :mentions do |t|
      t.references :discussion_entry, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: true
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.string :workflow_state, default: "active", null: false, limit: 255
      t.timestamps precision: 6

      t.replica_identity_index
    end

    create_table :messages do |t|
      t.text :to
      t.text :from
      t.text :subject
      t.text :body
      t.integer :delay_for, default: 120
      t.timestamp :dispatch_at
      t.timestamp :sent_at, index: { where: "sent_at IS NOT NULL" }
      t.string :workflow_state, limit: 255
      t.text :transmission_errors
      t.boolean :is_bounced
      t.references :notification
      t.references :communication_channel
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.references :user, index: false
      t.timestamps null: true, precision: nil
      t.string :notification_name, limit: 255
      t.text :url
      t.string :path_type, limit: 255
      t.text :from_name
      t.boolean :to_email
      t.text :html_body
      t.references :root_account
      t.string :reply_to_name, limit: 255

      t.index %i[context_id context_type notification_name to user_id], name: "existing_undispatched_message"
      t.index %i[user_id to_email dispatch_at], name: "index_messages_user_id_dispatch_at_to_email"
      t.index :created_at
    end

    create_table :microsoft_sync_groups do |t|
      t.references :course, foreign_key: true, index: { unique: true }, null: false
      t.string :workflow_state, null: false, default: "pending"
      t.string :job_state
      t.timestamp :last_synced_at
      t.timestamp :last_manually_synced_at
      t.text :last_error
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.timestamps precision: 6
      t.string :ms_group_id
      t.references :last_error_report, index: false
      t.text :debug_info

      t.replica_identity_index
    end

    create_table :microsoft_sync_partial_sync_changes do |t|
      t.references :course, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: true
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.string :enrollment_type, null: false
      t.timestamps precision: 6

      t.replica_identity_index
      t.index %i[course_id user_id enrollment_type],
              unique: true,
              name: "index_microsoft_sync_partial_sync_changes_course_user_enroll"
    end

    create_table :microsoft_sync_user_mappings do |t|
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.references :user, null: false, foreign_key: true, index: false
      t.string :aad_id
      t.timestamps precision: 6
      t.boolean :needs_updating, default: false, null: false

      t.replica_identity_index
      t.index [:user_id, :root_account_id], unique: true, name: "index_microsoft_sync_user_mappings_ra_id_user_id"
    end

    create_table :migration_issues do |t|
      t.references :content_migration, null: false, foreign_key: true
      t.text :description
      t.string :workflow_state, null: false, limit: 255
      t.text :fix_issue_html_url
      t.string :issue_type, null: false, limit: 255
      t.references :error_report, index: false
      t.text :error_message
      t.timestamps precision: nil
    end

    create_table :moderation_graders do |t|
      t.string :anonymous_id, limit: 5, null: false
      t.references :assignment, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: true, index: false
      t.timestamps precision: nil
      t.boolean :slot_taken, default: true, null: false

      t.index [:assignment_id, :anonymous_id], unique: true
      t.index [:user_id, :assignment_id], unique: true
    end

    create_table :moderated_grading_provisional_grades do |t|
      t.string :grade, limit: 255
      t.float :score
      t.timestamp :graded_at
      t.references :scorer, null: false, foreign_key: { to_table: :users }
      t.references :submission, null: false, foreign_key: true
      t.timestamps precision: nil
      t.boolean :final, null: false, default: false
      t.references :source_provisional_grade,
                   foreign_key: { to_table: :moderated_grading_provisional_grades,
                                  name: "provisional_grades_source_provisional_grade_fk" },
                   index: { where: "source_provisional_grade_id IS NOT NULL",
                            name: "index_provisional_grades_on_source_grade" }
      t.boolean :graded_anonymously

      t.index :submission_id,
              unique: true,
              where: "final = TRUE",
              name: "idx_mg_provisional_grades_unique_submission_when_final"
      t.index [:submission_id, :scorer_id],
              unique: true,
              name: "idx_mg_provisional_grades_unique_sub_scorer_when_not_final",
              where: "final = FALSE"
    end

    create_table :moderated_grading_selections do |t|
      t.references :assignment, null: false, foreign_key: true, index: false
      t.references :student, null: false, foreign_key: {  to_table: :users }
      t.references :selected_provisional_grade,
                   foreign_key: { to_table: :moderated_grading_provisional_grades },
                   index: { where: "selected_provisional_grade_id IS NOT NULL",
                            name: "index_moderated_grading_selections_on_selected_grade" }
      t.timestamps precision: nil

      t.index [:assignment_id, :student_id],
              unique: true,
              name: "idx_mg_selections_unique_on_assignment_and_student"
    end

    create_table :notification_endpoints do |t|
      t.references :access_token, null: false, foreign_key: true
      t.string :token, null: false, limit: 255
      t.string :arn, null: false, limit: 255
      t.timestamps precision: nil
      t.string :workflow_state, default: "active", null: false, index: true

      t.index [:access_token_id, :arn],
              where: "workflow_state='active'",
              unique: true
    end

    create_table :notifications do |t|
      t.string :name, limit: 255, index: { unique: true, name: "index_notifications_unique_on_name" }
      t.string :subject, limit: 255
      t.string :category, limit: 255
      t.integer :delay_for, default: 120
      t.timestamps precision: nil
      t.string :main_link, limit: 255
      t.boolean :priority, default: false, null: false
    end

    create_table :notification_policies do |t|
      t.references :notification
      t.references :communication_channel, null: false, foreign_key: true, index: false
      t.string :frequency, default: "immediately", null: false, limit: 255
      t.timestamps precision: nil

      t.index [:communication_channel_id, :notification_id], unique: true, name: "index_notification_policies_on_cc_and_notification_id"
    end

    create_table :notification_policy_overrides do |t|
      t.references :context,
                   polymorphic: { default: "Course" },
                   null: false,
                   index: { name: "index_notification_policy_overrides_on_context" }
      t.references :communication_channel, null: false, foreign_key: true
      t.references :notification
      t.string :workflow_state, default: "active", null: false
      t.string :frequency
      t.timestamps precision: nil

      t.index %i[communication_channel_id notification_id],
              name: "index_notification_policies_overrides_on_cc_id_and_notification"
      t.index %i[context_id context_type communication_channel_id notification_id],
              where: "notification_id IS NOT NULL",
              unique: true,
              name: "index_notification_policies_overrides_uniq_context_notification"
      t.index %i[context_id context_type communication_channel_id],
              where: "notification_id IS NULL",
              unique: true,
              name: "index_notification_policies_overrides_uniq_context_and_cc"
    end

    create_table :oauth_requests do |t|
      t.string :token, limit: 255
      t.string :secret, limit: 255
      t.string :user_secret, limit: 255
      t.string :return_url, limit: 4.kilobytes
      t.string :workflow_state, limit: 255
      t.references :user, foreign_key: true, index: { where: "user_id IS NOT NULL" }
      t.string :original_host_with_port, limit: 255
      t.string :service, limit: 255
      t.timestamps precision: nil
    end

    create_table :observer_alert_thresholds do |t|
      t.string :alert_type, null: false
      t.string :threshold
      t.string :workflow_state, default: "active", null: false
      t.timestamps precision: nil
      t.references :user, null: false, foreign_key: true
      t.references :observer, null: false, foreign_key: { to_table: :users }

      t.index %i[alert_type user_id observer_id], unique: true, name: "observer_alert_thresholds_on_alert_type_and_observer_and_user"
    end

    create_table :observer_alerts do |t|
      t.references :observer_alert_threshold, null: false, foreign_key: true
      t.references :context, polymorphic: true, index: { name: "index_observer_alerts_on_context_type_and_context_id" }
      t.string :alert_type, null: false
      t.string :workflow_state, default: "unread", null: false, index: true
      t.timestamp :action_date, null: false
      t.string :title, null: false
      t.timestamps precision: nil
      t.references :user, null: false, foreign_key: true
      t.references :observer, null: false, foreign_key: { to_table: :users }
    end

    create_table :observer_pairing_codes do |t|
      t.references :user, null: false, foreign_key: true
      t.string :code, null: false, limit: 10
      t.timestamp :expires_at, null: false, index: true
      t.string :workflow_state, default: "active", null: false
      t.timestamps precision: nil
    end

    create_table :one_time_passwords do |t|
      t.references :user, null: false, foreign_key: true, index: false
      t.string :code, null: false
      t.boolean :used, null: false, default: false
      t.timestamps precision: nil

      t.index [:user_id, :code], unique: true
    end

    create_table :originality_reports do |t|
      t.references :attachment
      t.float :originality_score
      t.references :originality_report_attachment
      t.text :originality_report_url
      t.text :originality_report_lti_url
      t.timestamps precision: nil
      t.references :submission, null: false, foreign_key: true
      t.string :workflow_state, null: false, default: "pending", index: true
      t.text :link_id
      t.text :error_message
      t.timestamp :submission_time, index: true
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.replica_identity_index
    end

    create_table :outcome_calculation_methods do |t|
      t.references :context,
                   polymorphic: { limit: 255 },
                   null: false,
                   index: { unique: true, name: "index_outcome_calculation_methods_on_context" }
      t.integer :calculation_int, limit: 2
      t.string :calculation_method, null: false, limit: 255
      t.string :workflow_state, null: false, default: "active"
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }
      t.timestamps precision: nil
    end

    create_table :outcome_friendly_descriptions do |t|
      t.references :context, polymorphic: { limit: 255 }, null: false, index: false
      t.string :workflow_state, null: false, default: "active"
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.text :description, null: false
      t.timestamps precision: nil
      t.references :learning_outcome, null: false, foreign_key: true

      t.index %i[context_type context_id learning_outcome_id], unique: true, name: "index_outcome_friendly_description_on_context_and_outcome"
    end

    create_table :outcome_imports do |t|
      t.string :workflow_state, null: false
      t.bigint :context_id, null: false
      t.string :context_type, null: false
      t.references :user, foreign_key: true
      t.references :attachment, foreign_key: true
      t.integer :progress
      t.timestamp :ended_at
      t.timestamps precision: nil
      t.json :data
      t.references :learning_outcome_group

      t.index %i[context_type context_id]
    end

    create_table :outcome_import_errors do |t|
      t.references :outcome_import, null: false, foreign_key: true
      t.string :message, null: false, limit: 255
      t.timestamps precision: nil
      t.integer :row
      t.boolean :failure, default: false, null: false
    end

    create_table :outcome_proficiencies do |t|
      t.timestamps precision: nil
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.bigint :context_id, null: false
      t.string :context_type, limit: 255, null: false
      t.string :workflow_state, default: "active", null: false

      t.index [:context_id, :context_type],
              unique: true,
              where: "context_id IS NOT NULL"
    end

    create_table :outcome_proficiency_ratings do |t|
      t.references :outcome_proficiency, null: false, foreign_key: true
      t.string :description, null: false, limit: 255
      t.float :points, null: false
      t.boolean :mastery, null: false
      t.string :color, null: false
      t.timestamps precision: nil
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.string :workflow_state, default: "active", null: false

      t.index [:outcome_proficiency_id, :points],
              name: "index_outcome_proficiency_ratings_on_proficiency_and_points"
    end

    create_table :page_comments do |t|
      t.text :message
      t.bigint :page_id
      t.string :page_type, limit: 255
      t.references :user, foreign_key: true
      t.timestamps precision: nil

      t.index [:page_id, :page_type]
    end

    create_table :page_views, id: false do |t|
      t.primary_keys [:request_id]

      t.string :request_id, limit: 255
      t.string :session_id, limit: 255
      t.references :user, null: false, foreign_key: true, index: false
      t.text :url
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.bigint :asset_id
      t.string :asset_type, limit: 255
      t.string :controller, limit: 255
      t.string :action, limit: 255
      t.float :interaction_seconds
      t.timestamps precision: nil
      t.references :developer_key, index: false
      t.boolean :user_request
      t.float :render_time
      t.text :user_agent
      t.references :asset_user_access, index: { name: "index_page_views_asset_user_access_id" }
      t.boolean :participated
      t.boolean :summarized
      t.references :account, index: false
      t.references :real_user, foreign_key: { to_table: :users }, index: { where: "real_user_id IS NOT NULL" }
      t.string :http_method, limit: 255
      t.string :remote_ip, limit: 255

      t.index [:account_id, :created_at]
      t.index [:context_type, :context_id]
      t.index [:summarized, :created_at], name: "index_page_views_summarized_created_at"
      t.index [:user_id, :created_at]
    end

    create_table :parallel_importers do |t|
      t.references :sis_batch, null: false, foreign_key: true
      t.string :workflow_state, null: false, limit: 255
      t.bigint :index, null: false
      t.bigint :batch_size, null: false
      t.timestamps precision: nil
      t.timestamp :started_at
      t.timestamp :ended_at
      t.string :importer_type, null: false, limit: 255
      t.references :attachment, null: false, foreign_key: true
      t.integer :rows_processed, default: 0, null: false
      t.bigint :job_ids, array: true, default: [], null: false
    end

    create_table :planner_notes do |t|
      t.timestamp :todo_date, null: false
      t.string :title, null: false
      t.text :details
      t.references :user, null: false, foreign_key: true
      t.references :course, index: false
      t.string :workflow_state, null: false
      t.timestamps precision: nil
      t.references :linked_object, polymorphic: true, index: false

      t.index %i[user_id linked_object_id linked_object_type],
              where: "linked_object_id IS NOT NULL AND workflow_state<>'deleted'",
              unique: true,
              name: "index_planner_notes_on_user_id_and_linked_object"
    end

    create_table :planner_overrides do |t|
      t.references :plannable, polymorphic: true, null: false, index: false
      t.references :user, null: false, foreign_key: true
      t.string :workflow_state
      t.boolean :marked_complete, null: false, default: false
      t.timestamp :deleted_at
      t.timestamps precision: nil
      t.boolean :dismissed, default: false, null: false

      t.index %i[plannable_type plannable_id user_id], unique: true, name: "index_planner_overrides_on_plannable_and_user"
    end

    create_table :plugin_settings do |t|
      t.string :name, default: "", null: false, limit: 255, index: true
      t.text :settings
      t.timestamps precision: nil
      t.boolean :disabled
    end

    create_table :polling_polls do |t|
      t.string :question, limit: 255
      t.string :description, limit: 255
      t.timestamps precision: nil
      t.references :user, null: false, foreign_key: true
    end

    create_table :polling_poll_choices do |t|
      t.string :text, limit: 255
      t.boolean :is_correct, null: false, default: false
      t.references :poll, null: false, foreign_key: { to_table: :polling_polls }
      t.timestamps precision: nil
      t.integer :position
    end

    create_table :polling_poll_sessions do |t|
      t.boolean :is_published, null: false, default: false
      t.boolean :has_public_results, null: false, default: false
      t.references :course, null: false, foreign_key: true
      t.references :course_section, foreign_key: true
      t.references :poll, null: false
      t.timestamps precision: nil
    end

    create_table :polling_poll_submissions do |t|
      t.references :poll, null: false, foreign_key: { to_table: :polling_polls }
      t.references :poll_choice, null: false, foreign_key: { to_table: :polling_poll_choices }
      t.references :user, null: false, foreign_key: true
      t.timestamps precision: nil
      t.references :poll_session, null: false, foreign_key: { to_table: :polling_poll_sessions }
    end

    create_table :post_policies do |t|
      t.boolean :post_manually, null: false, default: false
      t.references :course, foreign_key: true
      t.references :assignment, foreign_key: true
      t.index [:course_id, :assignment_id], unique: true
      t.timestamps precision: nil
      t.references :root_account, foreign_key: { to_table: :accounts }
    end

    create_table :profiles do |t|
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.references :context,
                   polymorphic: { limit: 255 },
                   null: false,
                   index: { unique: true, name: "index_profiles_on_context_type_and_context_id" }
      t.string :title, limit: 255
      t.string :path, limit: 255
      t.text :description
      t.text :data
      t.string :visibility, limit: 255
      t.integer :position

      t.index [:root_account_id, :path], unique: true
    end

    create_table :progresses do |t|
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.references :user, index: false
      t.string :tag, null: false, limit: 255
      t.float :completion
      t.string :delayed_job_id, limit: 255
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.text :message
      t.string :cache_key_context, limit: 255
      t.text :results

      t.index [:context_id, :context_type]
    end

    create_table :pseudonyms do |t|
      t.references :user, null: false, foreign_key: true
      t.references :account, null: false, foreign_key: true, index: false
      t.string :workflow_state, null: false, limit: 255
      t.string :unique_id, null: false, limit: 255
      t.string :crypted_password, null: false, limit: 255
      t.string :password_salt, null: false, limit: 255
      t.string :persistence_token, null: false, limit: 255, index: true
      t.string :single_access_token, null: false, limit: 255, index: true
      t.string :perishable_token, null: false, limit: 255
      t.integer :login_count, default: 0, null: false
      t.integer :failed_login_count, default: 0, null: false
      t.timestamp :last_request_at
      t.timestamp :last_login_at
      t.timestamp :current_login_at
      t.string :last_login_ip, limit: 255
      t.string :current_login_ip, limit: 255
      t.string :reset_password_token, default: "", null: false, limit: 255
      t.integer :position
      t.timestamps precision: nil
      t.boolean :password_auto_generated
      t.timestamp :deleted_at
      t.references :sis_batch, foreign_key: true, index: { where: "sis_batch_id IS NOT NULL" }
      t.string :sis_user_id, limit: 255
      t.string :sis_ssha, limit: 255
      t.references :communication_channel, index: false
      t.references :sis_communication_channel
      t.text :stuck_sis_fields
      t.string :integration_id, limit: 255
      t.references :authentication_provider, foreign_key: true, index: { where: "authentication_provider_id IS NOT NULL" }
      t.string :declared_user_type, limit: 255
      # the login_attribute the unique_id came from
      t.string :login_attribute, limit: 255, if_not_exists: true
      # a hash of all login attribute names to their value; allows migrating to
      # different login attributes in the future
      t.jsonb :unique_ids, null: false, default: {}, if_not_exists: true
      t.string :verification_token, limit: 255

      # login_attribute can only be set if authentication_provider_id is set
      # conversely, if authentication_provider_id IS NULL, login_attribute MUST be NULL
      t.check_constraint <<~SQL.squish, name: "chk_login_attribute_authentication_provider_id"
        authentication_provider_id IS NOT NULL OR login_attribute IS NULL
      SQL

      t.replica_identity_index :account_id
      if (trgm = connection.extension(:pg_trgm)&.schema)
        t.index "lower(sis_user_id) #{trgm}.gin_trgm_ops", name: "index_gin_trgm_pseudonyms_sis_user_id", using: :gin
        t.index "lower(unique_id) #{trgm}.gin_trgm_ops", name: "index_gin_trgm_pseudonyms_unique_id", using: :gin
      end
      t.index [:sis_user_id, :account_id], where: "sis_user_id IS NOT NULL", unique: true
      t.index [:integration_id, :account_id],
              unique: true,
              name: "index_pseudonyms_on_integration_id",
              where: "integration_id IS NOT NULL"
      t.index "LOWER(unique_id), account_id, authentication_provider_id",
              name: "index_pseudonyms_unique_without_login_attribute",
              unique: true,
              where: "workflow_state IN ('active', 'suspended') AND login_attribute IS NULL"
      t.index "LOWER(unique_id), account_id",
              name: "index_pseudonyms_unique_without_auth_provider",
              unique: true,
              where: "workflow_state IN ('active', 'suspended') AND authentication_provider_id IS NULL"
      t.index "LOWER(unique_id), account_id, authentication_provider_id, login_attribute",
              name: "index_pseudonyms_unique_with_login_attribute",
              unique: true,
              where: "workflow_state IN ('active', 'suspended')"
      t.index "LOWER(unique_id), account_id", name: "index_pseudonyms_on_unique_id_and_account_id"
    end

    create_table :purgatories do |t|
      t.references :attachment, null: false, foreign_key: true, index: { unique: true }
      t.references :deleted_by_user, foreign_key: { to_table: :users }
      t.timestamps precision: nil
      t.string :workflow_state, null: false, default: "active", index: true
      t.string :old_filename, null: false
      t.string :old_display_name, limit: 255
      t.string :old_content_type, limit: 255
      t.string :new_instfs_uuid
      t.string :old_file_state
      t.string :old_workflow_state
    end

    create_table :quizzes do |t|
      t.string :title, limit: 255
      t.text :description, limit: 16_777_215
      t.text :quiz_data, limit: 16_777_215
      t.float :points_possible
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.references :assignment, foreign_key: true, index: { unique: true }
      t.string :workflow_state, null: false, limit: 255
      t.boolean :shuffle_answers, default: false, null: false
      t.boolean :show_correct_answers, default: true, null: false
      t.integer :time_limit
      t.integer :allowed_attempts
      t.string :scoring_policy, limit: 255
      t.string :quiz_type, limit: 255
      t.timestamps precision: nil
      t.timestamp :lock_at
      t.timestamp :unlock_at
      t.timestamp :deleted_at
      t.boolean :could_be_locked, default: false, null: false
      t.references :cloned_item, foreign_key: true, index: { where: "cloned_item_id IS NOT NULL" }
      t.string :access_code, limit: 255
      t.string :migration_id, limit: 255
      t.integer :unpublished_question_count, default: 0
      t.timestamp :due_at
      t.integer :question_count
      t.references :last_assignment, index: false
      t.timestamp :published_at
      t.timestamp :last_edited_at
      t.boolean :anonymous_submissions, default: false, null: false
      t.references :assignment_group, index: false
      t.string :hide_results, limit: 255
      t.string :ip_filter, limit: 255
      t.boolean :require_lockdown_browser, default: false, null: false
      t.boolean :require_lockdown_browser_for_results, default: false, null: false
      t.boolean :one_question_at_a_time, default: false, null: false
      t.boolean :cant_go_back, default: false, null: false
      t.timestamp :show_correct_answers_at
      t.timestamp :hide_correct_answers_at
      t.boolean :require_lockdown_browser_monitor, default: false, null: false
      t.text :lockdown_browser_monitor_data
      t.boolean :only_visible_to_overrides, default: false, null: false
      t.boolean :one_time_results, default: false, null: false
      t.boolean :show_correct_answers_last_attempt, default: false, null: false
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.boolean :disable_timer_autosubmission, default: false, null: false

      t.index [:context_id, :context_type]
    end

    create_table :quiz_groups do |t|
      t.references :quiz, null: false
      t.string :name, limit: 255
      t.integer :pick_count
      t.float :question_points
      t.integer :position
      t.timestamps precision: nil
      t.string :migration_id, limit: 255
      t.references :assessment_question_bank, index: false
      t.references :root_account, foreign_key: { to_table: :accounts }
    end

    create_table :quiz_migration_alerts do |t|
      t.references :migration, polymorphic: true, index: { name: "index_quiz_migration_alerts_on_migration_type_and_migration_id" }
      t.references :user, null: false, foreign_key: true
      t.references :course, null: false, foreign_key: true
      t.timestamps precision: 6
    end

    create_table :quiz_questions do |t|
      t.references :quiz, index: false
      t.references :quiz_group, index: { name: "quiz_questions_quiz_group_id" }
      t.references :assessment_question, index: { where: "assessment_question_id IS NOT NULL" }
      t.text :question_data
      t.integer :assessment_question_version
      t.integer :position
      t.timestamps null: true, precision: nil
      t.string :migration_id, limit: 255
      t.string :workflow_state, limit: 255
      t.integer :duplicate_index
      t.references :root_account

      t.index [:quiz_id, :assessment_question_id], name: "idx_qqs_on_quiz_and_aq_ids"
      t.index %i[assessment_question_id quiz_group_id duplicate_index],
              name: "index_generated_quiz_questions",
              where: "assessment_question_id IS NOT NULL AND quiz_group_id IS NOT NULL AND workflow_state='generated'",
              unique: true
    end

    create_table :quiz_regrades do |t|
      t.references :user, null: false, foreign_key: true
      t.references :quiz, null: false, foreign_key: true, index: false
      t.integer :quiz_version, null: false
      t.timestamps precision: nil

      t.index [:quiz_id, :quiz_version], unique: true
    end

    create_table :quiz_question_regrades do |t|
      t.references :quiz_regrade, null: false, foreign_key: true, index: false
      t.references :quiz_question, null: false, foreign_key: true, index: { name: "index_qqr_on_qq_id" }
      t.string :regrade_option, null: false, limit: 255
      t.timestamps precision: nil

      t.index [:quiz_regrade_id, :quiz_question_id], unique: true, name: "index_qqr_on_qr_id_and_qq_id"
    end

    create_table :quiz_regrade_runs do |t|
      t.references :quiz_regrade, null: false, foreign_key: true
      t.timestamp :started_at
      t.timestamp :finished_at
      t.timestamps precision: nil
    end

    create_table :quiz_statistics do |t|
      t.references :quiz, foreign_key: true, index: false
      t.boolean :includes_all_versions
      t.boolean :anonymous
      t.timestamps precision: nil
      t.string :report_type, limit: 255
      t.boolean :includes_sis_ids

      t.index [:quiz_id, :report_type]
    end

    create_table :quiz_submissions do |t|
      t.references :quiz, null: false, foreign_key: true, index: false
      t.integer :quiz_version
      t.references :user, foreign_key: { deferrable: :immediate }
      t.text :submission_data, limit: 16_777_215
      t.references :submission
      t.float :score
      t.float :kept_score
      t.text :quiz_data, limit: 16_777_215
      t.timestamp :started_at
      t.timestamp :end_at
      t.timestamp :finished_at
      t.integer :attempt
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.integer :fudge_points, default: 0
      t.float :quiz_points_possible
      t.integer :extra_attempts
      t.string :temporary_user_code, limit: 255, index: true
      t.integer :extra_time
      t.boolean :manually_unlocked
      t.boolean :manually_scored
      t.string :validation_token, limit: 255
      t.float :score_before_regrade
      t.boolean :was_preview
      t.boolean :has_seen_results
      t.boolean :question_references_fixed
      t.references :root_account, foreign_key: { to_table: :accounts }

      t.index [:quiz_id, :user_id], unique: true
    end
    # If the column is created as a float with default 0, it becomes 0.0, which
    # would be fine, but it's easier to compare schema consistency this way.
    change_column :quiz_submissions, :fudge_points, :float

    create_table :quiz_submission_events do |t|
      t.integer :attempt, null: false
      t.string :event_type, null: false, limit: 255
      t.references :quiz_submission, null: false, foreign_key: true, index: false
      t.text :event_data
      t.timestamp :created_at, null: false, index: true
      t.timestamp :client_timestamp
      t.references :root_account, foreign_key: { to_table: :accounts }

      t.index %i[quiz_submission_id attempt created_at],
              name: "event_predecessor_locator_index"
    end

    create_table :quiz_submission_snapshots do |t|
      t.references :quiz_submission
      t.integer :attempt
      t.text :data
      t.timestamps null: true, precision: nil
    end

    create_table :report_snapshots do |t|
      t.string :report_type, limit: 255
      t.text :data, limit: 16_777_215
      t.timestamps precision: nil
      t.references :account, foreign_key: true, index: { where: "account_id IS NOT NULL" }

      t.index %i[report_type account_id created_at], name: "index_on_report_snapshots"
    end

    create_table :roles do |t|
      t.string :name, null: false, limit: 255, index: true
      t.string :base_role_type, null: false, limit: 255
      t.references :account, foreign_key: true
      t.string :workflow_state, null: false, limit: 255
      t.timestamps precision: nil
      t.timestamp :deleted_at
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false

      t.replica_identity_index
      t.index [:account_id, :name], unique: true, name: "index_roles_unique_account_name_where_active", where: "workflow_state = 'active'"
    end

    create_table :role_overrides do |t|
      t.string :permission, limit: 255
      t.boolean :enabled, default: true, null: false
      t.boolean :locked, default: false, null: false
      t.references :context, null: false, foreign_key: { to_table: :accounts }, index: false
      t.string :context_type, limit: 255, null: false
      t.timestamps null: true, precision: nil
      t.boolean :applies_to_self, default: true, null: false
      t.boolean :applies_to_descendants, default: true, null: false
      t.references :role, null: false, foreign_key: true
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.replica_identity_index
      t.index %i[context_id context_type role_id permission],
              unique: true,
              name: "index_role_overrides_on_context_role_permission"
    end

    create_table :rubrics do |t|
      t.references :user, foreign_key: true
      t.references :rubric, foreign_key: true, index: { where: "rubric_id IS NOT NULL" }
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.text :data
      t.float :points_possible
      t.string :title, limit: 255
      t.text :description
      t.timestamps precision: nil
      t.boolean :reusable, default: false
      t.boolean :public, default: false
      t.boolean :read_only, default: false
      t.integer :association_count, default: 0
      t.boolean :free_form_criterion_comments
      t.string :context_code, limit: 255
      t.string :migration_id, limit: 255
      t.boolean :hide_score_total
      t.string :workflow_state, default: "active", null: false, limit: 255
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.boolean :hide_points, default: false, null: false
      t.string :rating_order, default: "descending", null: false
      t.string :button_display, default: "numeric", null: false
      t.references :rubric_imports, foreign_key: true

      t.check_constraint "rating_order IN ('descending', 'ascending')", name: "chk_rating_order_enum"
      t.check_constraint "button_display IN ('numeric', 'emoji', 'letter')", name: "chk_button_display_enum"

      t.index [:context_id, :context_type]
    end

    create_table :rubric_associations do |t|
      t.references :rubric, null: false, foreign_key: true
      t.bigint :association_id, null: false
      t.string :association_type, null: false, limit: 255
      t.boolean :use_for_grading
      t.timestamps precision: nil
      t.string :title, limit: 255
      t.text :summary_data
      t.string :purpose, null: false, limit: 255
      t.string :url, limit: 255
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.boolean :hide_score_total
      t.boolean :bookmarked, default: true
      t.string :context_code, limit: 255, index: true
      t.boolean :hide_points, default: false
      t.boolean :hide_outcome_results, default: false
      t.references :root_account
      t.string :workflow_state, default: "active", null: false

      t.index [:association_id, :association_type], name: "index_rubric_associations_on_aid_and_atype"
      t.index [:context_id, :context_type]
    end

    create_table :rubric_assessments do |t|
      t.references :user, foreign_key: true
      t.references :rubric, null: false, foreign_key: true
      t.references :rubric_association, foreign_key: true
      t.float :score
      t.text :data
      t.timestamps precision: nil
      t.bigint :artifact_id, null: false
      t.string :artifact_type, null: false, limit: 255
      t.string :assessment_type, null: false, limit: 255
      t.references :assessor, foreign_key: { to_table: :users }
      t.integer :artifact_attempt
      t.boolean :hide_points, default: false, null: false
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.references :rubric_assessment_imports, foreign_key: true

      t.index [:artifact_id, :artifact_type]
    end

    create_table :rubric_assessment_imports do |t|
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.string :workflow_state, null: false
      t.references :user, foreign_key: true
      t.references :assignment, foreign_key: true
      t.references :attachment, foreign_key: true
      t.references :course, foreign_key: true
      t.integer :progress, default: 0, null: false
      t.integer :error_count, default: 0, null: false
      t.json :error_data
      t.timestamps

      t.replica_identity_index
    end

    create_table :rubric_criteria do |t|
      t.references :rubric, null: false, foreign_key: { to_table: :rubrics }
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.text :description
      t.text :long_description
      t.integer :order, null: false
      t.decimal :points, null: false
      t.boolean :criterion_use_range, null: false, default: false
      t.references :learning_outcome, foreign_key: { to_table: :learning_outcomes }
      t.decimal :mastery_points
      t.boolean :ignore_for_scoring, null: false, default: false
      t.string :workflow_state, null: false, default: "active", limit: 255
      t.references :created_by, null: false, foreign_key: { to_table: :users }
      t.references :deleted_by, foreign_key: { to_table: :users }
      t.timestamps

      t.replica_identity_index
    end

    create_table :rubric_imports do |t|
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.string :workflow_state, null: false
      t.references :user, foreign_key: true
      t.references :attachment, foreign_key: true
      t.integer :progress, default: 0, null: false
      t.integer :error_count, default: 0, null: false
      t.json :error_data
      t.timestamps
      t.references :account, foreign_key: true, index: { where: "account_id IS NOT NULL" }
      t.references :course, foreign_key: true, index: { where: "course_id IS NOT NULL" }

      t.check_constraint <<~SQL.squish, name: "chk_require_association"
        (account_id IS NOT NULL OR
        course_id IS NOT NULL) AND NOT
        (account_id IS NOT NULL AND course_id IS NOT NULL)
      SQL

      t.replica_identity_index
    end

    create_table :scheduled_smart_alerts do |t|
      t.string :context_type, null: false
      t.string :alert_type, null: false
      t.bigint :context_id, null: false
      t.timestamp :due_at, null: false, index: true
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }
      t.timestamps precision: nil

      t.index %i[context_type context_id alert_type root_account_id], name: "index_unique_scheduled_smart_alert"
    end

    create_table :scores do |t|
      t.references :enrollment, null: false, foreign_key: true, index: { name: "index_enrollment_scores" }
      t.references :grading_period, foreign_key: true, index: { where: "grading_period_id IS NOT NULL" }
      t.string :workflow_state, default: :active, null: false, limit: 255
      t.float :current_score
      t.float :final_score
      t.timestamps null: true, precision: nil
      t.references :assignment_group
      t.boolean :course_score, default: false, null: false
      t.float :unposted_current_score
      t.float :unposted_final_score
      t.float :current_points
      t.float :unposted_current_points
      t.float :final_points
      t.float :unposted_final_points
      t.float :override_score
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.references :custom_grade_status, foreign_key: true, index: { where: "custom_grade_status_id IS NOT NULL" }

      t.index %i[enrollment_id grading_period_id],
              unique: true,
              where: "grading_period_id IS NOT NULL",
              name: "index_grading_period_scores"
      t.index %i[enrollment_id assignment_group_id],
              unique: true,
              where: "assignment_group_id IS NOT NULL",
              name: "index_assignment_group_scores"
      t.index :enrollment_id, unique: true, where: "course_score", name: "index_course_scores"
    end

    create_table :score_metadata do |t|
      t.references :score, null: false, foreign_key: true, index: { unique: true }
      t.json :calculation_details, default: {}, null: false
      t.timestamps precision: nil
      t.string :workflow_state, default: "active", null: false
    end

    create_table :score_statistics do |t|
      t.references :assignment, null: false, index: { unique: true }, foreign_key: true
      t.float :minimum, null: false
      t.float :maximum, null: false
      t.float :mean, null: false
      t.integer :count, null: false
      t.timestamps precision: nil
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.float :lower_q
      t.float :median
      t.float :upper_q
    end

    create_table :sessions do |t|
      t.string :session_id, null: false, limit: 255, index: true
      t.text :data
      t.timestamps precision: nil

      t.index :updated_at
    end

    create_table :session_persistence_tokens do |t|
      t.string :token_salt, null: false, limit: 255
      t.string :crypted_token, null: false, limit: 255
      t.references :pseudonym, null: false, foreign_key: true
      t.timestamps precision: nil
    end

    create_table :settings do |t|
      t.string :name, limit: 255, index: { unique: true }
      t.text :value
      t.timestamps precision: nil
      t.boolean :secret, default: false, null: false
    end

    create_table :shared_brand_configs do |t|
      t.string :name, limit: 255
      t.references :account, foreign_key: true
      t.string :brand_config_md5, limit: 32, null: false, index: true
      t.timestamps precision: nil

      t.foreign_key :brand_configs, column: :brand_config_md5, primary_key: :md5
    end

    create_table :sis_batch_errors do |t|
      t.references :sis_batch, null: false, foreign_key: true
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }
      t.text :message, null: false
      t.text :backtrace
      t.string :file, limit: 255
      t.boolean :failure, default: false, null: false
      t.integer :row
      t.timestamp :created_at, null: false, index: true
      t.text :row_info
    end

    create_table :sis_batch_roll_back_data do |t|
      t.references :sis_batch, null: false, foreign_key: true
      t.references :context, polymorphic: { limit: 255 }, null: false, index: false
      t.string :previous_workflow_state, null: false, limit: 255
      t.string :updated_workflow_state, null: false, limit: 255
      t.boolean :batch_mode_delete, null: false, default: false
      t.string :workflow_state, null: false, limit: 255, default: "active", index: true
      t.timestamps precision: nil

      t.index %i[updated_workflow_state previous_workflow_state],
              name: "index_sis_batch_roll_back_context_workflow_states"
    end

    create_table :sis_post_grades_statuses do |t|
      t.references :course, null: false, foreign_key: true
      t.references :course_section, foreign_key: true
      t.references :user, foreign_key: true
      t.string :status, null: false, limit: 255
      t.string :message, null: false, limit: 255
      t.timestamp :grades_posted_at, null: false
      t.timestamps precision: nil
    end

    create_table :standard_grade_statuses do |t|
      t.string :color, limit: 7, null: false
      t.string :status_name, null: false
      t.boolean :hidden, default: false, null: false
      t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
      t.timestamps precision: 6

      t.replica_identity_index
      t.index [:status_name, :root_account_id], unique: true, name: "index_standard_status_on_name_and_root_account_id"
    end

    create_table :stream_items do |t|
      t.text :data, null: false
      t.timestamps precision: nil
      t.references :context, polymorphic: { limit: 255 }, index: false
      t.string :asset_type, null: false, limit: 255
      t.bigint :asset_id
      t.string :notification_category, limit: 255

      t.index [:asset_type, :asset_id], unique: true
      t.index :updated_at
    end

    create_table :stream_item_instances do |t|
      t.references :user, null: false, foreign_key: true, index: false
      t.references :stream_item, null: false
      t.boolean :hidden, default: false, null: false
      t.string :workflow_state, limit: 255
      t.references :context, polymorphic: { limit: 255 }, index: { name: "index_stream_item_instances_on_context_type_and_context_id" }

      t.index %i[user_id hidden id stream_item_id], name: "index_stream_item_instances_global"
      t.index [:stream_item_id, :user_id], unique: true
    end

    create_table :submissions do |t|
      t.text :body, limit: 16_777_215
      t.string :url, limit: 255
      t.references :attachment, index: false
      t.string :grade, limit: 255
      t.float :score
      t.timestamp :submitted_at, index: true
      t.references :assignment, null: false, foreign_key: true, index: false
      t.references :user, null: false, foreign_key: { deferrable: :immediate }, index: false
      t.string :submission_type, limit: 255
      t.string :workflow_state, null: false, limit: 255
      t.timestamps null: true, precision: nil
      t.references :group, foreign_key: true, index: { where: "group_id IS NOT NULL" }
      t.text :attachment_ids
      t.boolean :processed
      t.boolean :grade_matches_current_submission
      t.float :published_score
      t.string :published_grade, limit: 255
      t.timestamp :graded_at, index: { using: :brin }
      t.float :student_entered_score
      t.references :grader, index: false
      t.string :media_comment_id, limit: 255
      t.string :media_comment_type, limit: 255
      t.references :quiz_submission, foreign_key: true, index: { where: "quiz_submission_id IS NOT NULL" }
      t.integer :submission_comments_count
      t.integer :attempt
      t.references :media_object, foreign_key: true, index: { where: "media_object_id IS NOT NULL" }
      t.text :turnitin_data
      t.timestamp :cached_due_date, index: true
      t.boolean :excused
      t.boolean :graded_anonymously
      t.string :late_policy_status, limit: 16, index: { where: "workflow_state<>'deleted' AND late_policy_status IS NOT NULL" }
      t.decimal :points_deducted, precision: 6, scale: 2
      t.references :grading_period, foreign_key: true, index: { where: "grading_period_id IS NOT NULL" }
      t.bigint :seconds_late_override
      t.string :lti_user_id
      t.string :anonymous_id, limit: 5
      t.timestamp :last_comment_at
      t.integer :extra_attempts
      t.timestamp :posted_at
      t.boolean :cached_quiz_lti, default: false, null: false, index: true
      t.string :cached_tardiness, limit: 16
      t.references :course, foreign_key: true, index: false, null: false
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.boolean :redo_request, default: false, null: false
      t.uuid :resource_link_lookup_uuid
      t.references :proxy_submitter, foreign_key: { to_table: :users }
      t.references :custom_grade_status, foreign_key: true, index: { where: "custom_grade_status_id IS NOT NULL" }
      t.string :sticker, limit: 255
      t.integer :body_word_count
      t.string :lti_id, limit: 255

      t.index [:assignment_id, :submission_type]
      t.index [:user_id, :assignment_id], unique: true
      t.index [:assignment_id, :user_id]
      t.index :assignment_id, name: "index_submissions_needs_grading", where: <<~SQL.squish
        submissions.submission_type IS NOT NULL
        AND (submissions.excused = 'f' OR submissions.excused IS NULL)
        AND (submissions.workflow_state = 'pending_review'
          OR (submissions.workflow_state IN ('submitted', 'graded')
            AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission)
          )
        )
      SQL
      t.index [:assignment_id, :grading_period_id],
              name: "index_active_submissions",
              where: "workflow_state <> 'deleted'"
      t.index [:assignment_id, :grading_period_id],
              where: "workflow_state<>'deleted' AND grading_period_id IS NOT NULL",
              name: "index_active_submissions_gp"
      t.index %i[assignment_id anonymous_id], unique: true, where: "anonymous_id IS NOT NULL"
      t.index "user_id, GREATEST(submitted_at, created_at)", name: "index_submissions_on_user_and_greatest_dates"
      t.index :user_id,
              where: "(score IS NOT NULL AND workflow_state = 'graded') OR excused = TRUE",
              name: "index_submissions_graded_or_excused_on_user_id"
      t.index :assignment_id,
              where: "workflow_state <> 'deleted' AND ((score IS NOT NULL AND workflow_state = 'graded') OR excused = TRUE)",
              name: "index_submissions_graded_or_excused_on_assignment_id"
      t.index [:user_id, :cached_due_date]
      t.index [:user_id, :course_id]
      t.index [:user_id, :course_id],
              where: "(score IS NOT NULL OR grade IS NOT NULL) AND workflow_state<>'deleted'",
              name: "index_submissions_with_grade"
      t.index [:course_id, :cached_due_date]
      t.index :user_id, where: "late_policy_status='missing'", name: "index_on_submissions_missing_for_user"
      t.index :assignment_id, name: "index_graded_submissions_on_assignments", where: "workflow_state='graded'"
      t.index %i[assignment_id cached_due_date], where: "workflow_state <> 'deleted'"
    end

    create_table :submission_comments do |t|
      t.text :comment
      t.references :submission, foreign_key: true
      t.references :author, foreign_key: { to_table: :users }
      t.string :author_name, limit: 255
      t.string :group_comment_id, limit: 255
      t.timestamps precision: nil
      t.text :attachment_ids
      t.references :assessment_request, index: false
      t.string :media_comment_id, limit: 255
      t.string :media_comment_type, limit: 255
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.text :cached_attachments
      t.boolean :anonymous
      t.boolean :teacher_only_comment, default: false
      t.boolean :hidden, default: false
      t.references :provisional_grade, foreign_key: { to_table: :moderated_grading_provisional_grades }, index: { where: "provisional_grade_id IS NOT NULL" }
      t.boolean :draft, default: false, null: false, index: true
      t.timestamp :edited_at
      t.integer :attempt, index: true
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.string :workflow_state, default: "active", null: false

      t.index [:context_id, :context_type]
    end

    create_table :submission_drafts do |t|
      t.references :submission, null: false, foreign_key: true
      t.integer :submission_attempt, index: true, null: false
      t.text :body
      t.text :url
      t.string :active_submission_type
      # This is actually the media_id e.g. m-123456 rather than the media_object.id
      t.string :media_object_id
      t.references :context_external_tool, index: false
      t.text :lti_launch_url
      t.uuid :resource_link_lookup_uuid
    end

    create_table :submission_draft_attachments do |t|
      t.references :submission_draft, null: false
      t.references :attachment, null: false

      t.index [:submission_draft_id, :attachment_id],
              name: "index_submission_draft_and_attachment_unique",
              unique: true
    end

    create_table :submission_versions do |t|
      t.bigint :context_id
      t.string :context_type, limit: 255
      t.references :version
      t.references :user, index: false
      t.references :assignment, index: false
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.timestamps null: false, default: -> { "now()" }

      t.index %i[context_id version_id user_id assignment_id],
              name: "index_submission_versions",
              where: "context_type='Course'",
              unique: true
    end

    # rubocop:disable Rails/SquishedSQLHeredocs
    execute(<<~SQL)
      CREATE FUNCTION #{connection.quote_table_name("submission_comment_after_save_set_last_comment_at__tr_fn")} () RETURNS trigger AS $$
      BEGIN
        UPDATE submissions
        SET last_comment_at = (
           SELECT MAX(submission_comments.created_at) FROM submission_comments
            WHERE submission_comments.submission_id=submissions.id AND
            submission_comments.author_id <> submissions.user_id AND
            submission_comments.draft <> 't' AND
            submission_comments.provisional_grade_id IS NULL
        ) WHERE id = NEW.submission_id;
        RETURN NEW;
      END;
      $$ LANGUAGE plpgsql;
    SQL

    execute(<<~SQL)
      CREATE FUNCTION #{connection.quote_table_name("submission_comment_after_delete_set_last_comment_at__tr_fn")} () RETURNS trigger AS $$
      BEGIN
        UPDATE submissions
        SET last_comment_at = (
           SELECT MAX(submission_comments.created_at) FROM submission_comments
            WHERE submission_comments.submission_id=submissions.id AND
            submission_comments.author_id <> submissions.user_id AND
            submission_comments.draft <> 't' AND
            submission_comments.provisional_grade_id IS NULL
        ) WHERE id = OLD.submission_id;
        RETURN OLD;
      END;
      $$ LANGUAGE plpgsql;
    SQL
    # rubocop:enable Rails/SquishedSQLHeredocs

    set_search_path("submission_comment_after_save_set_last_comment_at__tr_fn", "()")
    set_search_path("submission_comment_after_delete_set_last_comment_at__tr_fn", "()")

    execute(<<~SQL.squish)
      CREATE TRIGGER submission_comment_after_insert_set_last_comment_at__tr
        AFTER INSERT ON #{SubmissionComment.quoted_table_name}
        FOR EACH ROW
        WHEN (NEW.draft <> 't' AND NEW.provisional_grade_id IS NULL)
        EXECUTE PROCEDURE #{connection.quote_table_name("submission_comment_after_save_set_last_comment_at__tr_fn")}()
    SQL

    execute(<<~SQL.squish)
      CREATE TRIGGER submission_comment_after_update_set_last_comment_at__tr
        AFTER UPDATE OF draft, provisional_grade_id ON #{SubmissionComment.quoted_table_name}
        FOR EACH ROW
        EXECUTE PROCEDURE #{connection.quote_table_name("submission_comment_after_save_set_last_comment_at__tr_fn")}()
    SQL

    execute(<<~SQL.squish)
      CREATE TRIGGER submission_comment_after_delete_set_last_comment_at__tr
        AFTER DELETE ON #{SubmissionComment.quoted_table_name}
        FOR EACH ROW
        WHEN (OLD.draft <> 't' AND OLD.provisional_grade_id IS NULL)
        EXECUTE PROCEDURE #{connection.quote_table_name("submission_comment_after_delete_set_last_comment_at__tr_fn")}()
    SQL

    create_table :switchman_shards do |t|
      t.string :name, limit: 255
      t.string :database_server_id, limit: 255, index: { unique: true,
                                                         where: "name IS NULL",
                                                         name: "index_switchman_shards_unique_primary_shard" }
      t.boolean :default, default: false, null: false, index: { unique: true, where: '"default"' }
      t.text :settings
      t.references :delayed_jobs_shard,
                   foreign_key: { to_table: :switchman_shards },
                   index: { where: "delayed_jobs_shard_id IS NOT NULL" }
      t.timestamps precision: nil
      t.boolean :block_stranded, default: false
      t.boolean :jobs_held, default: false

      t.index [:database_server_id, :name], unique: true
      t.index "(true)",
              unique: true,
              where: "database_server_id IS NULL AND name IS NULL",
              name: "index_switchman_shards_unique_primary_db_and_shard"
    end

    create_table :temporary_enrollment_pairings, if_not_exists: true do |t|
      t.references :root_account, foreign_key: { to_table: :accounts }, null: false, index: false
      t.string :workflow_state, null: false, default: "active", limit: 255
      t.timestamps
      t.references :created_by, foreign_key: { to_table: :users }
      t.references :deleted_by, foreign_key: { to_table: :users }
      t.text :ending_enrollment_state

      t.replica_identity_index
    end

    create_table :terms_of_service_contents do |t|
      t.text :content, null: false
      t.timestamps precision: nil
      t.timestamp :terms_updated_at, null: false
      t.string :workflow_state, null: false
      t.references :account, foreign_key: true, index: { unique: true }
    end

    create_table :terms_of_services do |t|
      t.string :terms_type, null: false, default: "default"
      t.boolean :passive, null: false, default: true
      t.references :terms_of_service_content, index: false
      t.references :account, null: false, foreign_key: true, index: { unique: true }
      t.timestamps precision: nil
      t.string :workflow_state, null: false
    end

    create_table :thumbnails do |t|
      t.references :parent
      t.string :content_type, null: false, limit: 255
      t.string :filename, null: false, limit: 255
      t.string :thumbnail, limit: 255
      t.integer :size, null: false
      t.integer :width
      t.integer :height
      t.timestamps precision: nil
      t.string :uuid, limit: 255
      t.string :namespace, limit: 255

      t.index [:parent_id, :thumbnail], unique: true, name: "index_thumbnails_size"
    end

    create_table :usage_rights do |t|
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.string :use_justification, null: false, limit: 255
      t.string :license, null: false, limit: 255
      t.text :legal_copyright

      t.index [:context_id, :context_type], name: "usage_rights_context_idx"
    end

    create_table :user_account_associations do |t|
      t.references :user, null: false, foreign_key: true, index: false
      t.references :account, null: false, foreign_key: true
      t.integer :depth
      t.timestamps precision: nil
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.replica_identity_index
      t.index [:user_id, :account_id], unique: true
    end

    create_table :user_lmgb_outcome_orderings do |t|
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.references :user, null: false, foreign_key: true
      t.references :course, null: false, foreign_key: true
      t.references :learning_outcome, null: false, foreign_key: true
      t.integer :position, null: false
      t.timestamps precision: 6

      t.replica_identity_index
      t.index %i[learning_outcome_id user_id course_id],
              unique: true,
              name: "index_user_lmgb_outcome_orderings"
    end

    create_table :user_merge_data do |t|
      t.references :user, null: false, foreign_key: true
      t.references :from_user, null: false
      t.timestamps precision: nil
      t.string :workflow_state, null: false, default: "active", limit: 255
    end

    create_table :user_merge_data_items do |t|
      t.references :user_merge_data, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: true
      t.string :item_type, null: false, limit: 255
      t.text :item, null: false
    end

    create_table :user_merge_data_records do |t|
      t.references :user_merge_data, null: false, foreign_key: true
      t.bigint :context_id, null: false
      t.references :previous_user, null: false, index: false
      t.string :context_type, null: false, limit: 255
      t.string :previous_workflow_state, limit: 255

      t.index %i[context_id context_type user_merge_data_id previous_user_id],
              name: "index_user_merge_data_records_on_context_id_and_context_type"
    end

    create_table :user_observers do |t|
      t.references :user, null: false, foreign_key: true, index: false
      t.references :observer, null: false, foreign_key: { to_table: :users }
      t.string :workflow_state, default: "active", null: false, limit: 255, index: true
      t.timestamps precision: nil
      t.references :sis_batch, index: { where: "sis_batch_id IS NOT NULL" }
      t.references :root_account, null: false, index: false

      t.index %i[user_id observer_id root_account_id],
              unique: true,
              name: "index_user_observers_on_user_id_and_observer_id_and_ra"
    end

    create_table :user_past_lti_ids do |t|
      t.references :user, null: false, foreign_key: true
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.string :user_uuid, null: false, limit: 255, index: true
      t.text :user_lti_id, null: false
      t.string :user_lti_context_id, limit: 255, index: true

      t.index %i[user_id context_id context_type], name: "user_past_lti_ids_index", unique: true
    end

    create_table :user_preference_values do |t|
      t.references :user, null: false, foreign_key: true, index: false
      t.string :key, null: false
      t.string :sub_key
      t.text :value

      t.index %i[user_id key sub_key], unique: true, name: "index_user_preference_values_on_keys"
      t.index [:user_id, :key],
              unique: true,
              where: "sub_key IS NULL",
              name: "index_user_preference_values_on_key_no_sub_key"
    end

    create_table :user_profiles do |t|
      t.text :bio
      t.string :title, limit: 255
      t.references :user, foreign_key: true, null: false
      t.string :pronunciation, limit: 255
    end

    create_table :user_profile_links do |t|
      t.string :url, limit: 4.kilobytes
      t.string :title, limit: 255
      t.references :user_profile, foreign_key: true, index: { where: "user_profile_id IS NOT NULL" }
      t.timestamps precision: nil
    end

    create_table :user_services do |t|
      t.references :user, null: false, foreign_key: true
      t.text :token
      t.string :secret, limit: 255
      t.string :protocol, limit: 255
      t.string :service, null: false, limit: 255
      t.timestamps precision: nil
      t.string :service_user_url, limit: 255
      t.string :service_user_id, null: false, limit: 255
      t.string :service_user_name, limit: 255
      t.string :service_domain, limit: 255
      t.string :crypted_password, limit: 255
      t.string :password_salt, limit: 255
      t.string :type, limit: 255
      t.string :workflow_state, null: false, limit: 255
      t.string :last_result_id, limit: 255
      t.timestamp :refresh_at
      t.boolean :visible
    end

    create_table :versions do |t|
      t.bigint :versionable_id
      t.string :versionable_type, limit: 255
      t.integer :number
      t.text :yaml, limit: 16_777_215
      t.timestamp :created_at

      t.index %i[versionable_id versionable_type number], unique: true, name: "index_versions_on_versionable_object_and_number"
    end

    create_table :viewed_submission_comments do |t|
      t.references :user, null: false, foreign_key: true, index: false
      t.references :submission_comment, null: false, foreign_key: true
      t.timestamp :viewed_at, null: false

      t.index [:user_id, :submission_comment_id], name: "index_viewed_submission_comments_user_comment", unique: true
    end

    create_table :web_conferences do |t|
      t.string :title, null: false, limit: 255
      t.string :conference_type, null: false, limit: 255
      t.string :conference_key, limit: 255
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.string :user_ids, limit: 255
      t.string :added_user_ids, limit: 255
      t.references :user, foreign_key: true
      t.timestamp :started_at
      t.text :description
      t.float :duration
      t.timestamps precision: nil
      t.string :uuid, limit: 255
      t.string :invited_user_ids, limit: 255
      t.timestamp :ended_at
      t.timestamp :start_at
      t.timestamp :end_at
      t.string :context_code, limit: 255
      t.string :type, limit: 255
      t.text :settings
      t.boolean :recording_ready
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.replica_identity_index
      t.index [:context_id, :context_type]
    end

    create_table :web_conference_participants do |t|
      t.references :user, foreign_key: true
      t.references :web_conference, foreign_key: true
      t.string :participation_type, limit: 255
      t.timestamps precision: nil
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false

      t.replica_identity_index
    end

    create_table :wikis do |t|
      t.string :title, limit: 255
      t.timestamps precision: nil
      t.text :front_page_url
      t.boolean :has_no_front_page
      t.references :root_account, foreign_key: { to_table: :accounts }
    end

    create_table :wiki_pages do |t|
      t.references :wiki, null: false, foreign_key: true
      t.string :title, limit: 255
      t.text :body, limit: 16_777_215
      t.string :workflow_state, null: false, limit: 255
      t.references :user, foreign_key: true
      t.timestamps precision: nil
      t.text :url
      t.boolean :protected_editing, default: false
      t.string :editing_roles, limit: 255
      t.timestamp :revised_at
      t.boolean :could_be_locked
      t.references :cloned_item, foreign_key: true, index: { where: "cloned_item_id IS NOT NULL" }
      t.string :migration_id, limit: 255
      t.references :assignment, foreign_key: true
      t.references :old_assignment, foreign_key: { to_table: :assignments }
      t.timestamp :todo_date
      t.bigint :context_id, null: false
      t.string :context_type, null: false
      t.references :root_account, foreign_key: { to_table: :accounts }
      t.timestamp :publish_at
      t.references :current_lookup, foreign_key: { to_table: :wiki_page_lookups }
      t.timestamp :unlock_at, precision: 6
      t.timestamp :lock_at, precision: 6
      t.boolean :only_visible_to_overrides, null: false, default: false

      t.index [:context_id, :context_type]
      t.index [:wiki_id, :todo_date], where: "todo_date IS NOT NULL"
      t.index %i[context_id context_type id]
    end

    create_table :wiki_page_lookups do |t|
      t.text :slug, null: false, index: false
      t.references :wiki_page, null: false, foreign_key: { deferrable: :deferred, on_delete: :cascade }
      t.references :root_account, foreign_key: { to_table: :accounts }, index: false, null: false
      t.bigint :context_id, null: false
      t.string :context_type, null: false, limit: 255
      t.timestamps precision: 6

      t.replica_identity_index
      t.index %i[context_id context_type slug],
              name: "unique_index_on_context_and_slug",
              unique: true
    end

    unless Rails.env.production?
      # this user is *not* used in production! it's only used to simulate a read-only secondary database in dev/test

      # the user is cluster-wide ...
      unless readonly_user_exists?
        execute("CREATE USER canvas_readonly_user")
      end

      quoted_schema = connection.quote_local_table_name(Shard.current.name)

      # ... but needs permissions on each shard's schema
      execute("GRANT USAGE ON SCHEMA #{quoted_schema} TO canvas_readonly_user")
      execute("GRANT SELECT ON ALL TABLES IN SCHEMA #{quoted_schema} TO canvas_readonly_user")
      execute("ALTER DEFAULT PRIVILEGES IN SCHEMA #{quoted_schema} GRANT SELECT ON TABLES TO canvas_readonly_user")
    end

    change_column :schema_migrations, :version, :string, limit: 255
  end

  def readonly_user_exists?
    !!connection.select_value("SELECT 1 AS one FROM pg_roles WHERE rolname='canvas_readonly_user'")
  end

  def down
    raise ActiveRecord::IrreversibleMigration
  end
end
# rubocop:enable Migration/ChangeColumn, Migration/Execute, Migration/IdColumn
# rubocop:enable Migration/PrimaryKey, Migration/RootAccountId, Rails/CreateTableWithTimestamps
# rubocop:enable Rails/ThreeStateBooleanColumn
