# frozen_string_literal: truerequire"stringio"moduleActiveRecord# = Active Record Schema Dumper## This class is used to dump the database schema for some connection to some# output format (i.e., ActiveRecord::Schema).classSchemaDumper# :nodoc:private_class_method:new### :singleton-method:# A list of tables which should not be dumped to the schema.# Acceptable values are strings as well as regexp if ActiveRecord.schema_format == :ruby.# Only strings are accepted if ActiveRecord.schema_format == :sql.cattr_accessor:ignore_tables,default: []### :singleton-method:# Specify a custom regular expression matching foreign keys which name# should not be dumped to db/schema.rb.cattr_accessor:fk_ignore_pattern,default: /^fk_rails_[0-9a-f]{10}$/### :singleton-method:# Specify a custom regular expression matching check constraints which name# should not be dumped to db/schema.rb.cattr_accessor:chk_ignore_pattern,default: /^chk_rails_[0-9a-f]{10}$/class<<selfdefdump(connection=ActiveRecord::Base.connection,stream=STDOUT,config=ActiveRecord::Base)connection.create_schema_dumper(generate_options(config)).dump(stream)streamendprivatedefgenerate_options(config){table_name_prefix: config.table_name_prefix,table_name_suffix: config.table_name_suffix}endenddefdump(stream)header(stream)extensions(stream)types(stream)tables(stream)trailer(stream)streamendprivateattr_accessor:table_namedefinitialize(connection,options={})@connection=connection@version=connection.migration_context.current_versionrescuenil@options=optionsend# turns 20170404131909 into "2017_04_04_131909"defformatted_versionstringified=@version.to_sreturnstringifiedunlessstringified.length==14stringified.insert(4,"_").insert(7,"_").insert(10,"_")enddefdefine_params@version?"version: #{formatted_version}":""enddefheader(stream)stream.puts<<~HEADER
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[#{ActiveRecord::Migration.current_version}].define(#{define_params}) do
HEADERenddeftrailer(stream)stream.puts"end"end# extensions are only supported by PostgreSQLdefextensions(stream)end# (enum) types are only supported by PostgreSQLdeftypes(stream)enddeftables(stream)sorted_tables=@connection.tables.sortsorted_tables.eachdo|table_name|table(table_name,stream)unlessignored?(table_name)end# dump foreign keys at the end to make sure all dependent tables exist.if@connection.supports_foreign_keys?sorted_tables.eachdo|tbl|foreign_keys(tbl,stream)unlessignored?(tbl)endendenddeftable(table,stream)columns=@connection.columns(table)beginself.table_name=tabletbl=StringIO.new# first dump primary key columnpk=@connection.primary_key(table)tbl.print" create_table #{remove_prefix_and_suffix(table).inspect}"casepkwhenStringtbl.print", primary_key: #{pk.inspect}"unlesspk=="id"pkcol=columns.detect{|c|c.name==pk}pkcolspec=column_spec_for_primary_key(pkcol)unlesspkcolspec.empty?ifpkcolspec!=pkcolspec.slice(:id,:default)pkcolspec={id: {type: pkcolspec.delete(:id),**pkcolspec}.compact}endtbl.print", #{format_colspec(pkcolspec)}"endwhenArraytbl.print", primary_key: #{pk.inspect}"elsetbl.print", id: false"endtable_options=@connection.table_options(table)iftable_options.present?tbl.print", #{format_options(table_options)}"endtbl.puts", force: :cascade do |t|"# then dump all non-primary key columnscolumns.eachdo|column|raiseStandardError,"Unknown type '#{column.sql_type}' for column '#{column.name}'"unless@connection.valid_type?(column.type)nextifcolumn.name==pktype,colspec=column_spec(column)iftype.is_a?(Symbol)tbl.print" t.#{type}#{column.name.inspect}"elsetbl.print" t.column #{column.name.inspect}, #{type.inspect}"endtbl.print", #{format_colspec(colspec)}"ifcolspec.present?tbl.putsendindexes_in_create(table,tbl)check_constraints_in_create(table,tbl)if@connection.supports_check_constraints?tbl.puts" end"tbl.putstbl.rewindstream.printtbl.readrescue=>estream.puts"# Could not dump table #{table.inspect} because of following #{e.class}"stream.puts"# #{e.message}"stream.putsensureself.table_name=nilendend# Keep it for indexing materialized viewsdefindexes(table,stream)if(indexes=@connection.indexes(table)).any?add_index_statements=indexes.mapdo|index|table_name=remove_prefix_and_suffix(index.table).inspect" add_index #{([table_name]+index_parts(index)).join(', ')}"endstream.putsadd_index_statements.sort.join("\n")stream.putsendenddefindexes_in_create(table,stream)if(indexes=@connection.indexes(table)).any?index_statements=indexes.mapdo|index|" t.index #{index_parts(index).join(', ')}"endstream.putsindex_statements.sort.join("\n")endenddefindex_parts(index)index_parts=[index.columns.inspect,"name: #{index.name.inspect}",]index_parts<<"unique: true"ifindex.uniqueindex_parts<<"length: #{format_index_parts(index.lengths)}"ifindex.lengths.present?index_parts<<"order: #{format_index_parts(index.orders)}"ifindex.orders.present?index_parts<<"opclass: #{format_index_parts(index.opclasses)}"ifindex.opclasses.present?index_parts<<"where: #{index.where.inspect}"ifindex.whereindex_parts<<"using: #{index.using.inspect}"if!@connection.default_index_type?(index)index_parts<<"type: #{index.type.inspect}"ifindex.typeindex_parts<<"comment: #{index.comment.inspect}"ifindex.commentindex_partsenddefcheck_constraints_in_create(table,stream)if(check_constraints=@connection.check_constraints(table)).any?add_check_constraint_statements=check_constraints.mapdo|check_constraint|parts=["t.check_constraint #{check_constraint.expression.inspect}"]ifcheck_constraint.export_name_on_schema_dump?parts<<"name: #{check_constraint.name.inspect}"end" #{parts.join(', ')}"endstream.putsadd_check_constraint_statements.sort.join("\n")endenddefforeign_keys(table,stream)if(foreign_keys=@connection.foreign_keys(table)).any?add_foreign_key_statements=foreign_keys.mapdo|foreign_key|parts=["add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}",remove_prefix_and_suffix(foreign_key.to_table).inspect,]ifforeign_key.column!=@connection.foreign_key_column_for(foreign_key.to_table)parts<<"column: #{foreign_key.column.inspect}"endifforeign_key.custom_primary_key?parts<<"primary_key: #{foreign_key.primary_key.inspect}"endifforeign_key.export_name_on_schema_dump?parts<<"name: #{foreign_key.name.inspect}"endparts<<"on_update: #{foreign_key.on_update.inspect}"ifforeign_key.on_updateparts<<"on_delete: #{foreign_key.on_delete.inspect}"ifforeign_key.on_deleteparts<<"deferrable: #{foreign_key.deferrable.inspect}"ifforeign_key.deferrable" #{parts.join(', ')}"endstream.putsadd_foreign_key_statements.sort.join("\n")endenddefformat_colspec(colspec)colspec.mapdo|key,value|"#{key}: #{value.is_a?(Hash)?"{ #{format_colspec(value)} }":value}"end.join(", ")enddefformat_options(options)options.map{|key,value|"#{key}: #{value.inspect}"}.join(", ")enddefformat_index_parts(options)ifoptions.is_a?(Hash)"{ #{format_options(options)} }"elseoptions.inspectendenddefremove_prefix_and_suffix(table)# This method appears at the top when profiling active_record test cases run.# Avoid costly calculation when there are no prefix and suffix.returntableif@options[:table_name_prefix].blank?&&@options[:table_name_suffix].blank?prefix=Regexp.escape(@options[:table_name_prefix].to_s)suffix=Regexp.escape(@options[:table_name_suffix].to_s)table.sub(/\A#{prefix}(.+)#{suffix}\z/,"\\1")enddefignored?(table_name)[ActiveRecord::Base.schema_migrations_table_name,ActiveRecord::Base.internal_metadata_table_name,ignore_tables].flatten.any?do|ignored|ignored===remove_prefix_and_suffix(table_name)endendendend