schema_creation.rb 5.2 KB
Newer Older
1 2
# frozen_string_literal: true

3
require "active_support/core_ext/string/strip"
4

5 6 7 8 9 10 11 12 13 14 15 16 17 18
module ActiveRecord
  module ConnectionAdapters
    class AbstractAdapter
      class SchemaCreation # :nodoc:
        def initialize(conn)
          @conn = conn
          @cache = {}
        end

        def accept(o)
          m = @cache[o.class] ||= "visit_#{o.class.name.split('::').last}"
          send m, o
        end

19
        delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
R
Ryuta Kamizono 已提交
20
          :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?, :foreign_key_options, to: :@conn
21
        private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
R
Ryuta Kamizono 已提交
22
          :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?, :foreign_key_options
23

24 25 26
        private

          def visit_AlterTable(o)
27
            sql = "ALTER TABLE #{quote_table_name(o.name)} ".dup
28 29 30
            sql << o.adds.map { |col| accept col }.join(" ")
            sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(" ")
            sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ")
31 32 33
          end

          def visit_ColumnDefinition(o)
34
            o.sql_type = type_to_sql(o.type, o.options)
35
            column_sql = "#{quote_column_name(o.name)} #{o.sql_type}".dup
R
Ryuta Kamizono 已提交
36
            add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key
37 38 39
            column_sql
          end

40
          def visit_AddColumnDefinition(o)
41
            "ADD #{accept(o.column)}".dup
42 43
          end

44
          def visit_TableDefinition(o)
45
            create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} ".dup
46 47 48 49

            statements = o.columns.map { |c| accept c }
            statements << accept(o.primary_keys) if o.primary_keys

50 51 52 53
            if supports_indexes_in_create?
              statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
            end

R
Ryuta Kamizono 已提交
54
            if supports_foreign_keys_in_create?
55 56 57
              statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
            end

58
            create_sql << "(#{statements.join(', ')})" if statements.present?
59
            add_table_options!(create_sql, table_options(o))
60
            create_sql << " AS #{to_sql(o.as)}" if o.as
61 62 63
            create_sql
          end

64 65 66 67
          def visit_PrimaryKeyDefinition(o)
            "PRIMARY KEY (#{o.name.join(', ')})"
          end

68
          def visit_ForeignKeyDefinition(o)
69
            sql = <<-SQL.strip_heredoc
70
              CONSTRAINT #{quote_column_name(o.name)}
71 72 73 74 75 76 77 78
              FOREIGN KEY (#{quote_column_name(o.column)})
                REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)})
            SQL
            sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete
            sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update
            sql
          end

79 80 81 82
          def visit_AddForeignKey(o)
            "ADD #{accept(o)}"
          end

83 84 85 86
          def visit_DropForeignKey(name)
            "DROP CONSTRAINT #{quote_column_name(name)}"
          end

87
          def table_options(o)
88 89 90 91
            table_options = {}
            table_options[:comment] = o.comment
            table_options[:options] = o.options
            table_options
92 93
          end

94 95 96 97
          def add_table_options!(create_sql, options)
            if options_sql = options[:options]
              create_sql << " #{options_sql}"
            end
98 99
          end

100
          def column_options(o)
101
            o.options.merge(column: o)
102 103 104
          end

          def add_column_options!(sql, options)
105
            sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
106 107 108 109 110 111 112
            # must explicitly check for :null to allow change_column to work on migrations
            if options[:null] == false
              sql << " NOT NULL"
            end
            if options[:auto_increment] == true
              sql << " AUTO_INCREMENT"
            end
R
Ryuta Kamizono 已提交
113 114 115
            if options[:primary_key] == true
              sql << " PRIMARY KEY"
            end
116 117 118
            sql
          end

119 120 121 122 123
          def to_sql(sql)
            sql = sql.to_sql if sql.respond_to?(:to_sql)
            sql
          end

124 125 126 127 128
          def foreign_key_in_create(from_table, to_table, options)
            options = foreign_key_options(from_table, to_table, options)
            accept ForeignKeyDefinition.new(from_table, to_table, options)
          end

Y
Yves Senn 已提交
129
          def action_sql(action, dependency)
130
            case dependency
131 132 133 134
            when :nullify then "ON #{action} SET NULL"
            when :cascade  then "ON #{action} CASCADE"
            when :restrict then "ON #{action} RESTRICT"
            else
135 136 137
              raise ArgumentError, <<-MSG.strip_heredoc
                '#{dependency}' is not supported for :on_update or :on_delete.
                Supported values are: :nullify, :cascade, :restrict
138
              MSG
139 140
            end
          end
141 142
      end
    end
143
    SchemaCreation = AbstractAdapter::SchemaCreation # :nodoc:
144 145
  end
end